#!/usr/bin/env bash

mkdir -p packages/app/src

cat <<'EOF' >mise.toml
[settings]
experimental = true

[task_config]
global_inputs = [
  "workspace.txt",
  "{{ config_root }}/templated.txt",
  "\\!important.txt",
  "@group:global-toolchain",
]

[task_config.cache]
enabled = true

[tasks.build]
dir = "packages/app"
run = '''
mkdir -p dist
printf '%s:%s:%s\n' \
  "$(cat src/input.txt)" \
  "$(cat ../../workspace.txt)" \
  "$(cat ../../toolchain.txt)" > dist/result.txt
printf 'ran\n' >> build-runs.txt
'''
sources = ["@group:app"]
outputs = ["dist"]

[tasks.check]
dir = "packages/app"
run = "printf 'ran\n' >> check-runs.txt"
sources = ["@group:app"]
outputs = []

[tasks.global-only]
run = "printf 'ran\n' >> global-only-runs.txt"
EOF

cat <<'EOF' >mise.local.toml
[task_config.input_groups]
global-toolchain = ["global-toolchain.txt"]
toolchain = ["toolchain.txt"]
app = [
  "packages/app/src/**/*",
  "packages/app/src/**/*.rs",
  "!packages/app/src/ignored/**/*",
  "packages/app/src/**/*.rs",
  "@group:toolchain",
]
EOF

printf 'source-one\n' >packages/app/src/input.txt
mkdir -p packages/app/src/ignored
printf 'excluded-one\n' >packages/app/src/ignored/excluded.txt
printf 'reincluded-one\n' >packages/app/src/ignored/reincluded.rs
printf 'workspace-one\n' >workspace.txt
printf 'templated-one\n' >templated.txt
printf 'escaped-one\n' >'!important.txt'
printf 'global-toolchain-one\n' >global-toolchain.txt
printf 'toolchain-one\n' >toolchain.txt

# Named groups and global inputs resolve independently across layered configs,
# and their paths are rooted at the config rather than each task's working directory.
mise run build ::: check ::: global-only
assert "wc -l < packages/app/build-runs.txt | tr -d ' '" "1"
assert "wc -l < packages/app/check-runs.txt | tr -d ' '" "1"
assert "wc -l < global-only-runs.txt | tr -d ' '" "1"
assert "cat packages/app/dist/result.txt" "source-one:workspace-one:toolchain-one"

# A task with no explicit sources or outputs receives the global inputs and
# automatic output freshness behavior.
mise run global-only
assert "wc -l < global-only-runs.txt | tr -d ' '" "1"

rm -rf packages/app/dist "$MISE_STATE_DIR/task-artifacts"
assert_contains "mise run build 2>&1" "restored outputs from cache"
assert "wc -l < packages/app/build-runs.txt | tr -d ' '" "1"
assert "cat packages/app/dist/result.txt" "source-one:workspace-one:toolchain-one"

# A grouped input invalidates every task that references the group.
printf 'source-two-changed\n' >packages/app/src/input.txt
rm -rf packages/app/dist
mise run build ::: check
assert "wc -l < packages/app/build-runs.txt | tr -d ' '" "2"
assert "wc -l < packages/app/check-runs.txt | tr -d ' '" "2"

# Global inputs invalidate eligible tasks without being repeated in sources.
printf 'workspace-two-changed\n' >workspace.txt
rm -rf packages/app/dist
mise run build ::: check ::: global-only
assert "wc -l < packages/app/build-runs.txt | tr -d ' '" "3"
assert "wc -l < packages/app/check-runs.txt | tr -d ' '" "3"
assert "wc -l < global-only-runs.txt | tr -d ' '" "2"

# A group referenced only from global inputs independently invalidates every task.
printf 'global-toolchain-two-changed\n' >global-toolchain.txt
rm -rf packages/app/dist
mise run build ::: check ::: global-only
assert "wc -l < packages/app/build-runs.txt | tr -d ' '" "4"
assert "wc -l < packages/app/check-runs.txt | tr -d ' '" "4"
assert "wc -l < global-only-runs.txt | tr -d ' '" "3"

# Nested groups participate in the cache key as normal source inputs.
printf 'toolchain-two-changed\n' >toolchain.txt
rm -rf packages/app/dist
mise run build ::: check ::: global-only
assert "wc -l < packages/app/build-runs.txt | tr -d ' '" "5"
assert "wc -l < packages/app/check-runs.txt | tr -d ' '" "5"
assert "wc -l < global-only-runs.txt | tr -d ' '" "3"
assert "cat packages/app/dist/result.txt" "source-two-changed:workspace-two-changed:toolchain-two-changed"

# Ordered patterns preserve a later repeated include after an exclusion.
printf 'reincluded-two-changed\n' >packages/app/src/ignored/reincluded.rs
rm -rf packages/app/dist
mise run build ::: check
assert "wc -l < packages/app/build-runs.txt | tr -d ' '" "6"
assert "wc -l < packages/app/check-runs.txt | tr -d ' '" "6"

# A file left excluded by the ordered patterns does not invalidate either task.
printf 'excluded-two-changed\n' >packages/app/src/ignored/excluded.txt
mise run build ::: check
assert "wc -l < packages/app/build-runs.txt | tr -d ' '" "6"
assert "wc -l < packages/app/check-runs.txt | tr -d ' '" "6"

# Templated global inputs are rendered before config-root anchoring.
printf 'templated-two-changed\n' >templated.txt
rm -rf packages/app/dist
mise run build ::: check
assert "wc -l < packages/app/build-runs.txt | tr -d ' '" "7"
assert "wc -l < packages/app/check-runs.txt | tr -d ' '" "7"

# Escaped-literal global inputs independently participate in invalidation.
printf 'escaped-two-changed\n' >'!important.txt'
rm -rf packages/app/dist
mise run build ::: check
assert "wc -l < packages/app/build-runs.txt | tr -d ' '" "8"
assert "wc -l < packages/app/check-runs.txt | tr -d ' '" "8"

# Group references are validated even when no input groups or global inputs
# are configured, rather than being retained as literal source paths.
cat <<'EOF' >mise.toml
[settings]
experimental = true

[tasks.invalid]
run = "echo invalid"
sources = ["@group:missing"]
EOF
rm mise.local.toml
assert_fail_contains "mise task info invalid" 'undefined input group "missing"'
