import re

import pytest

from hatch.config.constants import AppEnvVars
from hatch.env.plugin.interface import EnvironmentInterface
from hatch.project.core import Project
from hatch.utils.structures import EnvVars


class MockEnvironment(EnvironmentInterface):  # no cov
    PLUGIN_NAME = "mock"

    def find(self):
        pass

    def create(self):
        pass

    def remove(self):
        pass

    def exists(self):
        pass

    def install_project(self):
        pass

    def install_project_dev_mode(self):
        pass

    def dependencies_in_sync(self):
        pass

    def sync_dependencies(self):
        pass


class TestEnvVars:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.env_vars == environment.env_vars == {AppEnvVars.ENV_ACTIVE: "default"}

    def test_not_table(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"env-vars": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.env-vars` must be a mapping"):
            _ = environment.env_vars

    def test_value_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"env-vars": {"foo": 9000}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match="Environment variable `foo` of field `tool.hatch.envs.default.env-vars` must be a string"
        ):
            _ = environment.env_vars

    def test_correct(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"env-vars": {"foo": "bar"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.env_vars == {AppEnvVars.ENV_ACTIVE: "default", "foo": "bar"}

    def test_context_formatting(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"env-vars": {"foo": "{env:FOOBAZ}-{matrix:bar}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {"bar": "42"},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with EnvVars({"FOOBAZ": "baz"}):
            assert environment.env_vars == {AppEnvVars.ENV_ACTIVE: "default", "foo": "baz-42"}


class TestEnvInclude:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.env_include == environment.env_include == []

    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"env-include": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.env-include` must be an array"):
            _ = environment.env_include

    def test_pattern_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"env-include": [9000]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match="Pattern #1 of field `tool.hatch.envs.default.env-include` must be a string"
        ):
            _ = environment.env_include

    def test_correct(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"env-include": ["FOO*"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.env_include == ["HATCH_BUILD_*", "FOO*"]


class TestEnvExclude:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.env_exclude == environment.env_exclude == []

    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"env-exclude": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.env-exclude` must be an array"):
            _ = environment.env_exclude

    def test_pattern_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"env-exclude": [9000]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match="Pattern #1 of field `tool.hatch.envs.default.env-exclude` must be a string"
        ):
            _ = environment.env_exclude

    def test_correct(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"env-exclude": ["FOO*"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.env_exclude == ["FOO*"]


class TestPlatforms:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.platforms == environment.platforms == []

    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"platforms": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.platforms` must be an array"):
            _ = environment.platforms

    def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"platforms": [9000]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match="Platform #1 of field `tool.hatch.envs.default.platforms` must be a string"
        ):
            _ = environment.platforms

    def test_correct(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"platforms": ["macOS"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.platforms == ["macos"]


class TestSkipInstall:
    def test_default_project(self, temp_dir, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(temp_dir, config=config)
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )
        (temp_dir / "pyproject.toml").touch()

        with temp_dir.as_cwd():
            assert environment.skip_install is environment.skip_install is False

    def test_default_no_project(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.skip_install is environment.skip_install is True

    def test_not_boolean(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"skip-install": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.skip-install` must be a boolean"):
            _ = environment.skip_install

    def test_enable(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"skip-install": True}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.skip_install is True


class TestDevMode:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.dev_mode is environment.dev_mode is True

    def test_not_boolean(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"dev-mode": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.dev-mode` must be a boolean"):
            _ = environment.dev_mode

    def test_disable(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"dev-mode": False}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.dev_mode is False


class TestBuilder:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.builder is False

    def test_not_boolean(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"builder": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.builder` must be a boolean"):
            _ = environment.builder

    def test_enable(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"builder": True}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.builder is True


class TestFeatures:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.features == environment.features == []

    def test_invalid_type(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"features": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.features` must be an array of strings"):
            _ = environment.features

    def test_correct(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "optional-dependencies": {"foo-bar": [], "baz": []}},
            "tool": {"hatch": {"envs": {"default": {"features": ["Foo...Bar", "Baz", "baZ"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.features == ["baz", "foo-bar"]

    def test_feature_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "optional-dependencies": {"foo": [], "bar": []}},
            "tool": {"hatch": {"envs": {"default": {"features": [9000]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Feature #1 of field `tool.hatch.envs.default.features` must be a string"):
            _ = environment.features

    def test_feature_empty_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "optional-dependencies": {"foo": [], "bar": []}},
            "tool": {"hatch": {"envs": {"default": {"features": [""]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            ValueError, match="Feature #1 of field `tool.hatch.envs.default.features` cannot be an empty string"
        ):
            _ = environment.features

    def test_feature_undefined(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "optional-dependencies": {"foo": []}},
            "tool": {"hatch": {"envs": {"default": {"features": ["foo", "bar", ""]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            ValueError,
            match=(
                "Feature `bar` of field `tool.hatch.envs.default.features` is not defined in "
                "field `project.optional-dependencies`"
            ),
        ):
            _ = environment.features


class TestDependencyGroups:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.dependency_groups == []

    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"dependency-groups": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match="Field `tool.hatch.envs.default.dependency-groups` must be an array of strings"
        ):
            _ = environment.dependency_groups

    def test_correct(self, isolation, isolated_data_dir, platform, temp_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "dependency-groups": {"foo-bar": [], "baz": []},
            "tool": {"hatch": {"envs": {"default": {"dependency-groups": ["Foo...Bar", "Baz", "baZ"]}}}},
        }
        project = Project(isolation, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        assert environment.dependency_groups == ["baz", "foo-bar"]

    def test_group_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "dependency-groups": {"foo": [], "bar": []},
            "tool": {"hatch": {"envs": {"default": {"dependency-groups": [9000]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match="Group #1 of field `tool.hatch.envs.default.dependency-groups` must be a string"
        ):
            _ = environment.dependency_groups

    def test_group_empty_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "dependency-groups": {"foo": [], "bar": []},
            "tool": {"hatch": {"envs": {"default": {"dependency-groups": [""]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            ValueError, match="Group #1 of field `tool.hatch.envs.default.dependency-groups` cannot be an empty string"
        ):
            _ = environment.dependency_groups

    def test_group_undefined(self, isolation, isolated_data_dir, platform, temp_application):
        config = {
            "project": {
                "name": "my_app",
                "version": "0.0.1",
            },
            "dependency-groups": {"foo": []},
            "tool": {"hatch": {"envs": {"default": {"dependency-groups": ["foo", "bar", ""]}}}},
        }
        project = Project(isolation, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        with pytest.raises(
            ValueError,
            match=(
                "Group `bar` of field `tool.hatch.envs.default.dependency-groups` is not "
                "defined in field `dependency-groups`"
            ),
        ):
            _ = environment.dependency_groups


class TestDescription:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.description == environment.description == ""

    def test_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"description": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.description` must be a string"):
            _ = environment.description

    def test_correct(self, isolation, isolated_data_dir, platform, global_application):
        description = "foo"
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"description": description}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.description is description


class TestDependencies:
    def test_default(self, isolation, isolated_data_dir, platform, temp_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {"hatch": {"envs": {"default": {"skip-install": False}}}},
        }
        project = Project(isolation, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        assert environment.dependencies == environment.dependencies == ["dep1"]
        assert len(environment.dependencies) == len(environment.dependencies_complex)

    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {"hatch": {"envs": {"default": {"dependencies": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.dependencies` must be an array"):
            _ = environment.dependencies

    def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {"hatch": {"envs": {"default": {"dependencies": [9000]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match="Dependency #1 of field `tool.hatch.envs.default.dependencies` must be a string"
        ):
            _ = environment.dependencies

    def test_invalid(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {"hatch": {"envs": {"default": {"dependencies": ["foo^1"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            ValueError, match="Dependency #1 of field `tool.hatch.envs.default.dependencies` is invalid: .+"
        ):
            _ = environment.dependencies

    def test_extra_not_array(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {"hatch": {"envs": {"default": {"extra-dependencies": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.extra-dependencies` must be an array"):
            _ = environment.dependencies

    def test_extra_entry_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {"hatch": {"envs": {"default": {"extra-dependencies": [9000]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match="Dependency #1 of field `tool.hatch.envs.default.extra-dependencies` must be a string"
        ):
            _ = environment.dependencies

    def test_extra_invalid(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {"hatch": {"envs": {"default": {"extra-dependencies": ["foo^1"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            ValueError, match="Dependency #1 of field `tool.hatch.envs.default.extra-dependencies` is invalid: .+"
        ):
            _ = environment.dependencies

    def test_full(self, isolation, isolated_data_dir, platform, temp_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {
                "hatch": {
                    "envs": {
                        "default": {"skip-install": False, "dependencies": ["dep2"], "extra-dependencies": ["dep3"]}
                    }
                }
            },
        }
        project = Project(isolation, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        assert environment.dependencies == ["dep2", "dep3", "dep1"]

    def test_context_formatting(self, isolation, isolated_data_dir, platform, temp_application, uri_slash_prefix):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {
                "hatch": {
                    "envs": {
                        "default": {
                            "skip-install": False,
                            "dependencies": ["dep2"],
                            "extra-dependencies": ["proj @ {root:uri}"],
                        }
                    }
                }
            },
        }
        project = Project(isolation, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        normalized_path = str(isolation).replace("\\", "/")
        assert environment.dependencies == ["dep2", f"proj @ file:{uri_slash_prefix}{normalized_path}", "dep1"]

    def test_project_dependencies_context_formatting(
        self, temp_dir, isolated_data_dir, platform, temp_application, uri_slash_prefix
    ):
        """
        Regression test for context formatting in project dependencies.
        Ensures that dependencies in [project] section with context variables
        like {root:parent:uri} are properly formatted before creating Dependency objects.
        """
        # Create a sibling project
        sibling_project = temp_dir.parent / "sibling-project"
        sibling_project.mkdir(exist_ok=True)
        (sibling_project / "pyproject.toml").write_text(
            """\
    [build-system]
    requires = ["hatchling"]
    build-backend = "hatchling.build"

    [project]
    name = "sibling-project"
    version = "0.0.1"
    """
        )

        config = {
            "project": {
                "name": "my_app",
                "version": "0.0.1",
                "dependencies": ["sibling-project @ {root:parent:uri}/sibling-project"],
            },
            "tool": {"hatch": {"envs": {"default": {"skip-install": False}}}},
        }
        project = Project(temp_dir, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        normalized_parent_path = str(temp_dir.parent).replace("\\", "/")
        expected_dep = f"sibling-project @ file:{uri_slash_prefix}{normalized_parent_path}/sibling-project"

        # Verify the dependency was formatted correctly
        assert expected_dep in environment.dependencies

        # Verify we can access the path property without errors
        for dep in environment.project_dependencies_complex:
            if dep.name == "sibling-project":
                assert dep.path is not None
                assert "sibling-project" in str(dep.path)

    def test_full_skip_install(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {
                "hatch": {
                    "envs": {
                        "default": {"dependencies": ["dep2"], "extra-dependencies": ["dep3"], "skip-install": True}
                    }
                }
            },
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.dependencies == ["dep2", "dep3"]

    def test_full_skip_install_and_features(self, isolation, isolated_data_dir, platform, temp_application):
        config = {
            "project": {
                "name": "my_app",
                "version": "0.0.1",
                "dependencies": ["dep1"],
                "optional-dependencies": {"feat": ["dep4"]},
            },
            "tool": {
                "hatch": {
                    "envs": {
                        "default": {
                            "dependencies": ["dep2"],
                            "extra-dependencies": ["dep3"],
                            "skip-install": True,
                            "features": ["feat"],
                        }
                    }
                }
            },
        }
        project = Project(isolation, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        assert environment.dependencies == ["dep2", "dep3", "dep4"]

    def test_full_skip_install_and_dependency_groups(self, isolation, isolated_data_dir, platform, temp_application):
        config = {
            "project": {
                "name": "my_app",
                "version": "0.0.1",
                "dependencies": ["dep1"],
            },
            "dependency-groups": {
                "foo": ["dep5"],
                "bar": ["dep4", {"include-group": "foo"}],
            },
            "tool": {
                "hatch": {
                    "envs": {
                        "default": {
                            "dependencies": ["dep2"],
                            "extra-dependencies": ["dep3"],
                            "skip-install": True,
                            "dependency-groups": ["bar"],
                        }
                    }
                }
            },
        }
        project = Project(isolation, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        assert environment.dependencies == ["dep2", "dep3", "dep4", "dep5"]

    def test_full_no_dev_mode(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {
                "hatch": {
                    "envs": {"default": {"dependencies": ["dep2"], "extra-dependencies": ["dep3"], "dev-mode": False}}
                }
            },
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.dependencies == ["dep2", "dep3"]

    def test_builder(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "build-system": {"requires": ["dep2"]},
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {
                "hatch": {"envs": {"default": {"skip-install": False, "builder": True, "dependencies": ["dep3"]}}}
            },
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.dependencies == ["dep3", "dep2"]

    def test_workspace(self, temp_dir, isolated_data_dir, platform, temp_application):
        for i in range(3):
            project_file = temp_dir / f"foo{i}" / "pyproject.toml"
            project_file.parent.mkdir()
            project_file.write_text(
                f"""\
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "foo{i}"
version = "0.0.1"
dependencies = ["pkg-{i}"]

[project.optional-dependencies]
feature1 = ["pkg-feature-1{i}"]
feature2 = ["pkg-feature-2{i}"]
feature3 = ["pkg-feature-3{i}"]
"""
            )

        config = {
            "project": {"name": "my_app", "version": "0.0.1", "dependencies": ["dep1"]},
            "tool": {
                "hatch": {
                    "envs": {
                        "default": {
                            "skip-install": False,
                            "dependencies": ["dep2"],
                            "extra-dependencies": ["dep3"],
                            "workspace": {
                                "members": [
                                    {"path": "foo0", "features": ["feature1"]},
                                    {"path": "foo1", "features": ["feature1", "feature2"]},
                                    {"path": "foo2", "features": ["feature1", "feature2", "feature3"]},
                                ],
                            },
                        },
                    },
                },
            },
        }
        project = Project(temp_dir, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        assert environment.dependencies == [
            "dep2",
            "dep3",
            "pkg-0",
            "pkg-feature-10",
            "pkg-1",
            "pkg-feature-11",
            "pkg-feature-21",
            "pkg-2",
            "pkg-feature-12",
            "pkg-feature-22",
            "pkg-feature-32",
            "dep1",
        ]

    def test_self_referencing_dependency_with_extras(self, temp_dir, isolated_data_dir, platform, global_application):
        """Test that self-referencing dependencies with extras include the extra's dependencies."""
        project_dir = temp_dir / "my-app"
        project_dir.mkdir()

        (project_dir / "my_app").mkdir()
        (project_dir / "my_app" / "__init__.py").write_text("")
        (project_dir / "my_app" / "__about__.py").write_text('__version__ = "0.0.1"')

        config = {
            "project": {
                "name": "my-app",
                "version": "0.0.1",
                "dependencies": [],
                "optional-dependencies": {
                    "test": ["pytest>=7.0"],
                },
            },
            "tool": {
                "hatch": {
                    "envs": {
                        "dev": {
                            "skip-install": False,
                            "dependencies": ["my-app[test]"],
                        }
                    }
                }
            },
        }

        project = Project(project_dir, config=config)
        global_application.project = project

        environment = MockEnvironment(
            project_dir,
            project.metadata,
            "dev",
            project.config.envs["dev"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        # Get all dependencies as strings (what would be passed to pip)
        all_deps_str = [str(d) for d in environment.all_dependencies_complex]

        # Should have the local installation
        assert any("my-app" in dep and "file://" in dep for dep in all_deps_str)

        # Should have my-app[test] which will cause pip to install pytest
        assert any("pytest" in dep.lower() for dep in all_deps_str)

    def test_workspace_member_dependency_with_extras(self, temp_dir, isolated_data_dir, platform, temp_application):
        """A workspace member's extras must expand against that member's own optional-dependencies,
        not the root project's.
        """
        member_file = temp_dir / "member-pkg" / "pyproject.toml"
        member_file.parent.mkdir()
        member_file.write_text(
            """\
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "member-pkg"
version = "0.0.1"
dependencies = []

[project.optional-dependencies]
test = ["member-test-dep"]
"""
        )

        config = {
            "project": {
                "name": "my-app",
                "version": "0.0.1",
                "dependencies": [],
                "optional-dependencies": {
                    # Same extra name on the root project, with a *different* dependency,
                    # to prove the lookup uses the member's metadata not the root's.
                    "test": ["root-test-dep"],
                },
            },
            "tool": {
                "hatch": {
                    "envs": {
                        "default": {
                            "skip-install": False,
                            "dependencies": ["member-pkg[test]"],
                            "workspace": {"members": [{"path": "member-pkg"}]},
                        },
                    },
                },
            },
        }

        project = Project(temp_dir, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        all_deps_str = [str(d) for d in environment.all_dependencies_complex]

        # The member's own extra must be expanded
        assert any("member-test-dep" in dep for dep in all_deps_str)
        # The root project's same-named extra must NOT leak into the env
        assert not any("root-test-dep" in dep for dep in all_deps_str)

    def test_self_referencing_dependency_with_extras_and_metadata_hook(
        self, temp_dir, isolated_data_dir, platform, global_application, helpers
    ):
        """Regression test for https://github.com/pypa/hatch/issues/2252.

        A configured metadata hook must not cause extras of self-referencing dependencies to be dropped.
        The hook here only sets ``description``; ``optional-dependencies`` remain static and the extras
        of ``my-app[test]`` must still expand to ``pytest>=7.0``.
        """
        project_dir = temp_dir / "my-app"
        project_dir.mkdir()

        (project_dir / "my_app").mkdir()
        (project_dir / "my_app" / "__init__.py").write_text("")
        (project_dir / "my_app" / "__about__.py").write_text('__version__ = "0.0.1"')

        (project_dir / "hatch_build.py").write_text(
            helpers.dedent(
                """
                from hatchling.metadata.plugin.interface import MetadataHookInterface

                class CustomHook(MetadataHookInterface):
                    def update(self, metadata):
                        metadata['description'] = 'set by hook'
                """
            )
        )

        config = {
            "project": {
                "name": "my-app",
                "version": "0.0.1",
                "dynamic": ["description"],
                "dependencies": [],
                "optional-dependencies": {
                    "test": ["pytest>=7.0"],
                },
            },
            "tool": {
                "hatch": {
                    "metadata": {"hooks": {"custom": {}}},
                    "envs": {
                        "dev": {
                            "skip-install": False,
                            "dependencies": ["my-app[test]"],
                        }
                    },
                }
            },
        }

        project = Project(project_dir, config=config)
        global_application.project = project

        environment = MockEnvironment(
            project_dir,
            project.metadata,
            "dev",
            project.config.envs["dev"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        all_deps_str = [str(d) for d in environment.all_dependencies_complex]

        # The self-referenced project must still be installed locally
        assert any("my-app" in dep and "file://" in dep for dep in all_deps_str)

        # And the extras must be expanded even though a metadata hook is configured
        assert any("pytest" in dep.lower() for dep in all_deps_str)

    def test_dev_mode_true_returns_editable(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify dev-mode=true creates editable local dependency."""
        # Create a pyproject.toml file so skip_install defaults to False
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
    [project]
    name = "my-app"
    version = "0.0.1"

    [tool.hatch.envs.default]
    dev-mode = true
    """)

        project = Project(temp_dir)
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        local_deps = environment.local_dependencies_complex

        assert len(local_deps) == 1
        assert local_deps[0].editable is True

    def test_dev_mode_false_returns_non_editable(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify dev-mode=false creates non-editable local dependency."""
        # Create a pyproject.toml file so skip_install defaults to False
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
    [project]
    name = "my-app"
    version = "0.0.1"

    [tool.hatch.envs.default]
    dev-mode = false
    """)

        project = Project(temp_dir)
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        local_deps = environment.local_dependencies_complex

        assert len(local_deps) == 1
        assert local_deps[0].editable is False

    def test_skip_install_returns_empty(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify skip-install=true returns empty local dependencies."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
    [project]
    name = "my-app"
    version = "0.0.1"

    [tool.hatch.envs.default]
    skip-install = true
    """)

        project = Project(temp_dir)
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        local_deps = environment.local_dependencies_complex

        assert len(local_deps) == 0

    def test_workspace_members_always_editable(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify workspace members are always editable regardless of dev-mode."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
    [project]
    name = "my-app"
    version = "0.0.1"

    [tool.hatch.envs.default]
    dev-mode = false
    """)

        project = Project(temp_dir)
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        local_deps = environment.local_dependencies_complex

        # Project itself should respect dev-mode=false
        assert len(local_deps) == 1
        assert local_deps[0].editable is False

    def test_dependency_group_resolution(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Test dependency group resolution."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
    [project]
    name = "my-app"
    version = "0.0.1"

    [dependency-groups]
    test = ["pytest"]

    [tool.hatch.envs.default]
    dependency-groups = ["test"]
    """)

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        deps = environment.project_dependencies_complex
        assert any("pytest" in str(d) for d in deps)

    def test_dependency_group_resolution_builder_false_dev_mode_false(
        self, temp_dir, isolated_data_dir, platform, temp_application
    ):
        """Test dependency group resolution in non-builder non-dev-mode environments."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
    [project]
    name = "my-app"
    version = "0.0.1"

    [dependency-groups]
    test = ["pytest"]

    [tool.hatch.envs.default]
    builder = false
    dev-mode = false
    dependency-groups = ["test"]
    """)

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        assert any("pytest" in str(d) for d in environment.project_dependencies_complex)
        assert any("pytest" in str(d) for d in environment.dependencies_complex)

    def test_dependency_group_resolution_builder_true_dev_mode_false(
        self, temp_dir, isolated_data_dir, platform, temp_application
    ):
        """Test dependency group resolution in builder non-dev-mode environments."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
    [project]
    name = "my-app"
    version = "0.0.1"

    [dependency-groups]
    test = ["pytest"]

    [tool.hatch.envs.default]
    builder = true
    dev-mode = false
    dependency-groups = ["test"]
    """)

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        assert any("pytest" in str(d) for d in environment.project_dependencies_complex)
        assert any("pytest" in str(d) for d in environment.dependencies_complex)

    def test_dependency_group_resolution_builder_true_dev_mode_true(
        self, temp_dir, isolated_data_dir, platform, temp_application
    ):
        """Test dependency group resolution in builder dev-mode environments."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
    [project]
    name = "my-app"
    version = "0.0.1"

    [dependency-groups]
    test = ["pytest"]

    [tool.hatch.envs.default]
    builder = true
    dev-mode = true
    dependency-groups = ["test"]
    """)

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        assert any("pytest" in str(d) for d in environment.project_dependencies_complex)
        assert any("pytest" in str(d) for d in environment.dependencies_complex)

    def test_additional_dependencies_as_strings(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Test additional_dependencies with string values."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
    [project]
    name = "my-app"
    version = "0.0.1"
    """)

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        environment.additional_dependencies = ["extra-dep"]
        deps = environment.dependencies_complex
        assert any("extra-dep" in str(d) for d in deps)


class TestScripts:
    @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
    def test_not_table(self, isolation, isolated_data_dir, platform, global_application, field):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {field: 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match=f"Field `tool.hatch.envs.default.{field}` must be a table"):
            _ = environment.scripts

    @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
    def test_name_contains_spaces(self, isolation, isolated_data_dir, platform, global_application, field):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {field: {"foo bar": []}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            ValueError,
            match=f"Script name `foo bar` in field `tool.hatch.envs.default.{field}` must not contain spaces",
        ):
            _ = environment.scripts

    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.scripts == environment.scripts == {}

    @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
    def test_single_commands(self, isolation, isolated_data_dir, platform, global_application, field):
        script_config = {"foo": "command1", "bar": "command2"}
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {field: script_config}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.scripts == {"foo": ["command1"], "bar": ["command2"]}

    @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
    def test_multiple_commands(self, isolation, isolated_data_dir, platform, global_application, field):
        script_config = {"foo": "command1", "bar": ["command3", "command2"]}
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {field: script_config}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.scripts == {"foo": ["command1"], "bar": ["command3", "command2"]}

    @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
    def test_multiple_commands_not_string(self, isolation, isolated_data_dir, platform, global_application, field):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {field: {"foo": [9000]}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match=f"Command #1 in field `tool.hatch.envs.default.{field}.foo` must be a string"
        ):
            _ = environment.scripts

    @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
    def test_config_invalid_type(self, isolation, isolated_data_dir, platform, global_application, field):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {field: {"foo": 9000}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match=f"Field `tool.hatch.envs.default.{field}.foo` must be a string or an array of strings"
        ):
            _ = environment.scripts

    @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
    def test_command_expansion_basic(self, isolation, isolated_data_dir, platform, global_application, field):
        script_config = {"foo": "command1", "bar": ["command3", "foo"]}
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {field: script_config}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.scripts == {"foo": ["command1"], "bar": ["command3", "command1"]}

    @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
    def test_command_expansion_multiple_nested(self, isolation, isolated_data_dir, platform, global_application, field):
        script_config = {
            "foo": "command3",
            "baz": ["command5", "bar", "foo", "command1"],
            "bar": ["command4", "foo", "command2"],
        }
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {field: script_config}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.scripts == {
            "foo": ["command3"],
            "baz": ["command5", "command4", "command3", "command2", "command3", "command1"],
            "bar": ["command4", "command3", "command2"],
        }

    @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
    def test_command_expansion_multiple_nested_ignore_exit_code(
        self, isolation, isolated_data_dir, platform, global_application, field
    ):
        script_config = {
            "foo": "command3",
            "baz": ["command5", "- bar", "foo", "command1"],
            "bar": ["command4", "- foo", "command2"],
        }
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {field: script_config}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.scripts == {
            "foo": ["command3"],
            "baz": ["command5", "- command4", "- command3", "- command2", "command3", "command1"],
            "bar": ["command4", "- command3", "command2"],
        }

    @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
    def test_command_expansion_modification(self, isolation, isolated_data_dir, platform, global_application, field):
        script_config = {
            "foo": "command3",
            "baz": ["command5", "bar world", "foo", "command1"],
            "bar": ["command4", "foo hello", "command2"],
        }
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {field: script_config}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.scripts == {
            "foo": ["command3"],
            "baz": ["command5", "command4 world", "command3 hello world", "command2 world", "command3", "command1"],
            "bar": ["command4", "command3 hello", "command2"],
        }

    def test_command_expansion_circular_inheritance(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": "bar", "bar": "foo"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            ValueError,
            match="Circular expansion detected for field `tool.hatch.envs.default.scripts`: foo -> bar -> foo",
        ):
            _ = environment.scripts

    def test_extra_less_precedence(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {
                "hatch": {
                    "envs": {
                        "default": {
                            "extra-scripts": {"foo": "command4", "baz": "command3"},
                            "scripts": {"foo": "command1", "bar": "command2"},
                        }
                    }
                },
            },
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.scripts == {"foo": ["command1"], "bar": ["command2"], "baz": ["command3"]}


class TestPreInstallCommands:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.pre_install_commands == environment.pre_install_commands == []

    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"pre-install-commands": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.pre-install-commands` must be an array"):
            _ = environment.pre_install_commands

    def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"pre-install-commands": [9000]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match="Command #1 of field `tool.hatch.envs.default.pre-install-commands` must be a string"
        ):
            _ = environment.pre_install_commands

    def test_correct(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"pre-install-commands": ["baz test"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.pre_install_commands == ["baz test"]


class TestPostInstallCommands:
    def test_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.post_install_commands == environment.post_install_commands == []

    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"post-install-commands": 9000}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.post-install-commands` must be an array"):
            _ = environment.post_install_commands

    def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"post-install-commands": [9000]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError, match="Command #1 of field `tool.hatch.envs.default.post-install-commands` must be a string"
        ):
            _ = environment.post_install_commands

    def test_correct(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"post-install-commands": ["baz test"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.post_install_commands == ["baz test"]


class TestEnvVarOption:
    def test_unset(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.get_env_var_option("foo") == ""

    def test_set(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with EnvVars({"HATCH_ENV_TYPE_MOCK_FOO": "bar"}):
            assert environment.get_env_var_option("foo") == "bar"


class TestContextFormatting:
    def test_env_name(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": "command {env_name}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert list(environment.expand_command("foo")) == ["command default"]

    def test_env_type(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": "command {env_type}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert list(environment.expand_command("foo")) == ["command mock"]

    def test_verbosity_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": "command -v={verbosity}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            9000,
            global_application,
        )

        assert list(environment.expand_command("foo")) == ["command -v=9000"]

    def test_verbosity_unknown_modifier(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": "command {verbosity:bar}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(ValueError, match="Unknown verbosity modifier: bar"):
            next(environment.expand_command("foo"))

    def test_verbosity_flag_adjustment_not_integer(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": "command {verbosity:flag:-1.0}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Verbosity flag adjustment must be an integer: -1.0"):
            next(environment.expand_command("foo"))

    @pytest.mark.parametrize(
        ("verbosity", "command"),
        [
            (-9000, "command -qqq"),
            (-3, "command -qqq"),
            (-2, "command -qq"),
            (-1, "command -q"),
            (0, "command"),
            (1, "command -v"),
            (2, "command -vv"),
            (3, "command -vvv"),
            (9000, "command -vvv"),
        ],
    )
    def test_verbosity_flag_default(
        self, isolation, isolated_data_dir, platform, global_application, verbosity, command
    ):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": "command {verbosity:flag}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            verbosity,
            global_application,
        )

        assert list(environment.expand_command("foo")) == [command]

    @pytest.mark.parametrize(
        ("adjustment", "command"),
        [
            (-9000, "command -qqq"),
            (-3, "command -qqq"),
            (-2, "command -qq"),
            (-1, "command -q"),
            (0, "command"),
            (1, "command -v"),
            (2, "command -vv"),
            (3, "command -vvv"),
            (9000, "command -vvv"),
        ],
    )
    def test_verbosity_flag_adjustment(
        self, isolation, isolated_data_dir, platform, global_application, adjustment, command
    ):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": f"command {{verbosity:flag:{adjustment}}}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert list(environment.expand_command("foo")) == [command]

    def test_verbosity_flag_adjustment_invalid(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Test verbosity flag with invalid adjustment."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
    [project]
    name = "my-app"
    version = "0.0.1"

    [tool.hatch.envs.default]
    scripts.test = "pytest {verbosity:flag:invalid}"
    """)

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        with pytest.raises(TypeError, match="Verbosity flag adjustment must be an integer"):
            list(environment.expand_command("test"))

    def test_args_undefined(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": "command {args}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert list(environment.expand_command("foo")) == ["command"]

    def test_args_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": "command {args: -bar > /dev/null}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert list(environment.expand_command("foo")) == ["command  -bar > /dev/null"]

    def test_args_default_override(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"scripts": {"foo": "command {args: -bar > /dev/null}"}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert list(environment.expand_command("foo baz")) == ["command baz"]

    def test_matrix_no_selection(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"dependencies": ["pkg=={matrix}"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(ValueError, match="The `matrix` context formatting field requires a modifier"):
            _ = environment.dependencies

    def test_matrix_no_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"dependencies": ["pkg=={matrix:bar}"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(ValueError, match="Nonexistent matrix variable must set a default: bar"):
            _ = environment.dependencies

    def test_matrix_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"dependencies": ["pkg=={matrix:bar:9000}"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.dependencies == ["pkg==9000"]

    def test_matrix_default_override(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"dependencies": ["pkg=={matrix:bar:baz}"]}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {"bar": "42"},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.dependencies == ["pkg==42"]

    def test_env_vars_override(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {
                "hatch": {
                    "envs": {
                        "default": {
                            "dependencies": ["pkg{env:DEP_PIN}"],
                            "env-vars": {"DEP_PIN": "==0.0.1"},
                            "overrides": {"env": {"DEP_ANY": {"env-vars": "DEP_PIN="}}},
                        },
                    },
                },
            },
        }
        with EnvVars({"DEP_ANY": "true"}):
            project = Project(isolation, config=config)
            environment = MockEnvironment(
                isolation,
                project.metadata,
                "default",
                project.config.envs["default"],
                {},
                isolated_data_dir,
                isolated_data_dir,
                platform,
                0,
                global_application,
            )

            assert environment.dependencies == ["pkg"]


class TestWorkspaceConfig:
    def test_not_table(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": 9000}}}},
        }
        project = Project(isolation, config=config)
        with pytest.raises(TypeError, match="Field workspace must be a table"):
            MockEnvironment(
                isolation,
                project.metadata,
                "default",
                project.config.envs["default"],  # Exception raised here
                {},
                isolated_data_dir,
                isolated_data_dir,
                platform,
                0,
                global_application,
            )

    def test_parallel_not_boolean(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"parallel": 9000}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(TypeError, match="Field `tool.hatch.envs.default.workspace.parallel` must be a boolean"):
            _ = environment.workspace.parallel

    def test_parallel_default(self, isolation, isolated_data_dir, platform, global_application):
        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.workspace.parallel is True

    def test_parallel_override(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"parallel": False}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.workspace.parallel is False

    def test_members_not_table(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": 9000}}}}},
        }
        project = Project(isolation, config=config)
        with pytest.raises(TypeError, match="Field workspace.members must be an array"):
            MockEnvironment(
                isolation,
                project.metadata,
                "default",
                project.config.envs["default"],  # Exception raised here
                {},
                isolated_data_dir,
                isolated_data_dir,
                platform,
                0,
                global_application,
            )

    def test_member_invalid_type(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [9000]}}}}},
        }
        project = Project(isolation, config=config)
        with pytest.raises(TypeError, match="Member #1 must be a string or table"):
            MockEnvironment(
                isolation,
                project.metadata,
                "default",
                project.config.envs["default"],  # Exception raised here
                {},
                isolated_data_dir,
                isolated_data_dir,
                platform,
                0,
                global_application,
            )

    def test_member_no_path(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{}]}}}}},
        }
        project = Project(isolation, config=config)
        with pytest.raises(TypeError, match="Member #1 must define a `path` key"):
            MockEnvironment(
                isolation,
                project.metadata,
                "default",
                project.config.envs["default"],  # Exception raised here
                {},
                isolated_data_dir,
                isolated_data_dir,
                platform,
                0,
                global_application,
            )

    def test_member_path_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": 9000}]}}}}},
        }
        project = Project(isolation, config=config)
        with pytest.raises(TypeError, match="Member #1 path must be a string"):
            MockEnvironment(
                isolation,
                project.metadata,
                "default",
                project.config.envs["default"],  # Exception raised here
                {},
                isolated_data_dir,
                isolated_data_dir,
                platform,
                0,
                global_application,
            )

    def test_member_path_empty_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": ""}]}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            ValueError,
            match=(
                "Option `path` of member #1 of field `tool.hatch.envs.default.workspace.members` "
                "cannot be an empty string"
            ),
        ):
            _ = environment.workspace.members

    def test_member_features_not_array(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": "foo", "features": 9000}]}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError,
            match=(
                "Option `features` of member #1 of field `tool.hatch.envs.default.workspace.members` "
                "must be an array of strings"
            ),
        ):
            _ = environment.workspace.members

    def test_member_feature_not_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": "foo", "features": [9000]}]}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            TypeError,
            match=(
                "Feature #1 of option `features` of member #1 of field `tool.hatch.envs.default.workspace.members` "
                "must be a string"
            ),
        ):
            _ = environment.workspace.members

    def test_member_feature_empty_string(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": "foo", "features": [""]}]}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            ValueError,
            match=(
                "Feature #1 of option `features` of member #1 of field `tool.hatch.envs.default.workspace.members` "
                "cannot be an empty string"
            ),
        ):
            _ = environment.workspace.members

    def test_member_feature_duplicate(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {
                "hatch": {
                    "envs": {"default": {"workspace": {"members": [{"path": "foo", "features": ["foo", "Foo"]}]}}}
                }
            },
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            ValueError,
            match=(
                "Feature #2 of option `features` of member #1 of field `tool.hatch.envs.default.workspace.members` "
                "is a duplicate"
            ),
        ):
            _ = environment.workspace.members

    def test_member_does_not_exist(self, isolation, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": "foo"}]}}}}},
        }
        project = Project(isolation, config=config)
        environment = MockEnvironment(
            isolation,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        with pytest.raises(
            OSError,
            match=re.escape(
                f"No members could be derived from `foo` of field `tool.hatch.envs.default.workspace.members`: "
                f"{isolation / 'foo'}"
            ),
        ):
            _ = environment.workspace.members

    def test_member_not_project(self, temp_dir, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": "foo"}]}}}}},
        }
        project = Project(temp_dir, config=config)
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        member_path = temp_dir / "foo"
        member_path.mkdir()

        with pytest.raises(
            OSError,
            match=re.escape(
                f"Member derived from `foo` of field `tool.hatch.envs.default.workspace.members` is not a project "
                f"(no `pyproject.toml` file): {member_path}"
            ),
        ):
            _ = environment.workspace.members

    def test_member_duplicate(self, temp_dir, isolated_data_dir, platform, global_application):
        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": "foo"}, {"path": "f*"}]}}}}},
        }
        project = Project(temp_dir, config=config)
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        member_path = temp_dir / "foo"
        member_path.mkdir()
        (member_path / "pyproject.toml").touch()

        with pytest.raises(
            ValueError,
            match=re.escape(
                f"Member derived from `f*` of field "
                f"`tool.hatch.envs.default.workspace.members` is a duplicate: {member_path}"
            ),
        ):
            _ = environment.workspace.members

    def test_correct(self, hatch, temp_dir, isolated_data_dir, platform, global_application):
        member1_path = temp_dir / "foo"
        member2_path = temp_dir / "bar"
        member3_path = temp_dir / "baz"
        for member_path in [member1_path, member2_path, member3_path]:
            with temp_dir.as_cwd():
                result = hatch("new", member_path.name)
                assert result.exit_code == 0, result.output

        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": "foo"}, {"path": "b*"}]}}}}},
        }
        project = Project(temp_dir, config=config)
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        members = environment.workspace.members
        assert len(members) == 3
        assert members[0].project.location == member1_path
        assert members[1].project.location == member2_path
        assert members[2].project.location == member3_path

    def test_member_outside_root_with_shared_prefix(self, temp_dir, isolated_data_dir, platform, global_application):
        """Verify correct workspace member discovery with shared path prefix.

        os.path.commonprefix works character-by-character, so for paths that
        share a partial directory name (e.g. 'local_app' and 'lib_member' both
        start with 'l'), it would return an invalid path like '.../l' instead
        of the true common ancestor directory. os.path.commonpath correctly
        returns the nearest common directory, which is what we need as the
        base for the member glob search.

        Example of the mismatch:
            os.path.commonprefix(['/usr/lib', '/usr/local/lib']) == '/usr/l'
            os.path.commonpath(['/usr/lib', '/usr/local/lib'])   == '/usr'
        """
        project_root = temp_dir / "local_app"
        project_root.mkdir()

        member_path = temp_dir / "lib_member"
        member_path.mkdir()
        (member_path / "pyproject.toml").write_text(
            """\
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "lib-member"
version = "0.1.0"
"""
        )

        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": "../lib_member"}]}}}}},
        }
        project = Project(project_root, config=config)
        environment = MockEnvironment(
            project_root,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        members = environment.workspace.members
        assert len(members) == 1
        assert members[0].project.location == member_path


class TestWorkspaceDependencies:
    def test_basic(self, temp_dir, isolated_data_dir, platform, global_application):
        for i in range(3):
            project_file = temp_dir / f"foo{i}" / "pyproject.toml"
            project_file.parent.mkdir()
            project_file.write_text(
                f"""\
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "foo{i}"
version = "0.0.1"
dependencies = ["pkg-{i}"]
"""
            )

        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": "f*"}]}}}}},
        }
        project = Project(temp_dir, config=config)
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.workspace.get_dependencies() == ["pkg-0", "pkg-1", "pkg-2"]

    def test_features(self, temp_dir, isolated_data_dir, platform, global_application):
        for i in range(3):
            project_file = temp_dir / f"foo{i}" / "pyproject.toml"
            project_file.parent.mkdir()
            project_file.write_text(
                f"""\
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "foo{i}"
version = "0.0.1"
dependencies = ["pkg-{i}"]

[project.optional-dependencies]
feature1 = ["pkg-feature-1{i}"]
feature2 = ["pkg-feature-2{i}"]
feature3 = ["pkg-feature-3{i}"]
"""
            )

        config = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {
                "hatch": {
                    "envs": {
                        "default": {
                            "workspace": {
                                "members": [
                                    {"path": "foo0", "features": ["feature1"]},
                                    {"path": "foo1", "features": ["feature1", "feature2"]},
                                    {"path": "foo2", "features": ["feature1", "feature2", "feature3"]},
                                ],
                            },
                        },
                    },
                },
            },
        }
        project = Project(temp_dir, config=config)
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            global_application,
        )

        assert environment.workspace.get_dependencies() == [
            "pkg-0",
            "pkg-feature-10",
            "pkg-1",
            "pkg-feature-11",
            "pkg-feature-21",
            "pkg-2",
            "pkg-feature-12",
            "pkg-feature-22",
            "pkg-feature-32",
        ]


class TestDependencyHash:
    def test_hash_includes_local_dependencies(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify dependency hash includes local dependencies."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[project]
name = "my-app"
version = "0.0.1"
""")

        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(temp_dir, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        hash_value = environment.dependency_hash()
        assert hash_value
        assert len(hash_value) > 0

    def test_hash_stable_when_dependencies_unchanged(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify dependency hash is stable when dependencies don't change."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[project]
name = "my-app"
version = "0.0.1"
""")

        config = {"project": {"name": "my_app", "version": "0.0.1"}}
        project = Project(temp_dir, config=config)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        hash1 = environment.dependency_hash()
        hash2 = environment.dependency_hash()

        assert hash1 == hash2

    def test_hash_changes_with_extra_dependencies(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify dependency hash changes when extra-dependencies are added."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[project]
name = "my-app"
version = "0.0.1"
""")

        config_no_deps = {"project": {"name": "my_app", "version": "0.0.1"}}
        project_no_deps = Project(temp_dir, config=config_no_deps)
        project_no_deps.set_app(temp_application)
        temp_application.project = project_no_deps
        env_no_deps = MockEnvironment(
            temp_dir,
            project_no_deps.metadata,
            "default",
            project_no_deps.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )
        hash_no_deps = env_no_deps.dependency_hash()

        config_with_deps = {
            "project": {"name": "my_app", "version": "0.0.1"},
            "tool": {"hatch": {"envs": {"default": {"extra-dependencies": ["pytest"]}}}},
        }
        project_with_deps = Project(temp_dir, config=config_with_deps)
        project_with_deps.set_app(temp_application)
        temp_application.project = project_with_deps
        env_with_deps = MockEnvironment(
            temp_dir,
            project_with_deps.metadata,
            "default",
            project_with_deps.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )
        hash_with_deps = env_with_deps.dependency_hash()

        assert hash_no_deps != hash_with_deps


class TestLocalDependenciesComplex:
    def test_dev_mode_true_returns_editable(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify dev-mode=true creates editable local dependency."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[project]
name = "my-app"
version = "0.0.1"
""")

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        local_deps = environment.local_dependencies_complex
        assert len(local_deps) == 1
        assert local_deps[0].editable is True

    def test_dev_mode_false_returns_non_editable(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify dev-mode=false creates non-editable local dependency."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[project]
name = "my-app"
version = "0.0.1"

[tool.hatch.envs.default]
dev-mode = false
""")

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        local_deps = environment.local_dependencies_complex
        assert len(local_deps) == 1
        assert local_deps[0].editable is False

    def test_workspace_members_always_editable(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify workspace members are always editable regardless of dev-mode."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[project]
name = "my-app"
version = "0.0.1"

[tool.hatch.envs.default]
dev-mode = false
workspace.members = ["member"]
""")

        member_dir = temp_dir / "member"
        member_dir.mkdir()
        (member_dir / "pyproject.toml").write_text("""
[project]
name = "member"
version = "0.0.1"
""")

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        local_deps = environment.local_dependencies_complex
        assert len(local_deps) == 2
        project_dep = next(d for d in local_deps if d.name == "my-app")
        member_dep = next(d for d in local_deps if d.name == "member")
        assert project_dep.editable is False
        assert member_dep.editable is True


class TestDynamicDependencies:
    def test_dynamic_dependencies_resolved(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify dynamic dependencies are resolved correctly."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[project]
name = "my-app"
version = "0.0.1"
dynamic = ["dependencies"]

[tool.hatch.metadata]
allow-direct-references = true
""")

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        assert "dependencies" in environment.metadata.dynamic


class TestBuildSystemIntegration:
    def test_builder_includes_build_requirements(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify builder environment includes build system requirements."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[build-system]
requires = ["hatchling", "build-dep"]

[project]
name = "my-app"
version = "0.0.1"

[tool.hatch.envs.build]
builder = true
""")

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "build",
            project.config.envs["build"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        deps = environment.dependencies
        assert any("hatchling" in d for d in deps)
        assert any("build-dep" in d for d in deps)


class TestEnvironmentLifecycle:
    def test_app_status_contexts(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Verify environment lifecycle status contexts work correctly."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[project]
name = "my-app"
version = "0.0.1"
""")

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        with environment.app_status_creation():
            pass
        with environment.app_status_pre_installation():
            pass
        with environment.app_status_post_installation():
            pass
        with environment.app_status_project_installation():
            pass
        with environment.app_status_dependency_state_check():
            pass
        with environment.app_status_dependency_installation_check():
            pass
        with environment.app_status_dependency_synchronization():
            pass


class TestFileSystemContext:
    def test_join_creates_new_context(self, temp_dir, isolated_data_dir, platform, temp_application):
        """Test FileSystemContext.join creates proper paths."""
        from hatch.env.plugin.interface import FileSystemContext

        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[project]
name = "my-app"
version = "0.0.1"
""")

        project = Project(temp_dir)
        project.set_app(temp_application)
        temp_application.project = project
        environment = MockEnvironment(
            temp_dir,
            project.metadata,
            "default",
            project.config.envs["default"],
            {},
            isolated_data_dir,
            isolated_data_dir,
            platform,
            0,
            temp_application,
        )

        ctx = FileSystemContext(environment, local_path=temp_dir, env_path="/env")
        new_ctx = ctx.join("subdir")
        assert "subdir" in str(new_ctx.local_path)
        assert "subdir" in new_ctx.env_path
