#!/usr/bin/env bash

mkdir -p packages/app/src

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

[task_config]
cascade = true
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"]
workspace-lock = ["Cargo.lock"]
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
printf 'parent-lock-one\n' >Cargo.lock

# 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"

# https://github.com/jdx/mise/discussions/12481
# Cascaded task inputs keep the root that defined each field.
mkdir -p cascade-child
cat <<'EOF' >cascade-child/mise.toml
[tasks.child]
run = "printf 'ran\\n' >> child-runs.txt"
sources = ["@group:workspace-lock"]
outputs = []
EOF
printf 'child-lock-one\n' >cascade-child/Cargo.lock
assert "mise -C cascade-child task info child --json | jq -r '.sources[] | select(endswith(\"/Cargo.lock\"))'" "$PWD/Cargo.lock"
assert "mise -C cascade-child task info child --json | jq -r '.sources[] | select(endswith(\"/workspace.txt\"))'" "$PWD/workspace.txt"

mise -C cascade-child run child
mise -C cascade-child run child
assert "wc -l < cascade-child/child-runs.txt | tr -d ' '" "1"
printf 'parent-lock-two\n' >Cargo.lock
mise -C cascade-child run child
assert "wc -l < cascade-child/child-runs.txt | tr -d ' '" "2"
printf 'child-lock-two\n' >cascade-child/Cargo.lock
mise -C cascade-child run child
assert "wc -l < cascade-child/child-runs.txt | tr -d ' '" "2"

# A local global_inputs value overrides that field without losing the inherited groups.
mkdir -p cascade-global-override
cat <<'EOF' >cascade-global-override/mise.toml
[task_config]
global_inputs = ["child-global.txt"]

[tasks.child]
run = "true"
sources = ["@group:workspace-lock"]
outputs = []
EOF
printf 'child-global\n' >cascade-global-override/child-global.txt
assert "mise -C cascade-global-override task info child --json | jq -r '.sources[] | select(endswith(\"/Cargo.lock\"))'" "$PWD/Cargo.lock"
assert "mise -C cascade-global-override task info child --json | jq -r '.sources[] | select(endswith(\"/child-global.txt\"))'" "$PWD/cascade-global-override/child-global.txt"

# Local groups merge by name, override matching groups from the child root,
# and leave inherited global inputs unchanged.
mkdir -p cascade-local
cat <<'EOF' >cascade-local/mise.toml
[task_config.input_groups]
workspace-lock = ["Cargo.lock"]
child-only = ["child-group.txt"]
global-toolchain = ["child-global-toolchain.txt"]

[tasks.child]
run = "true"
sources = ["@group:workspace-lock", "@group:toolchain", "@group:child-only"]
outputs = []
EOF
printf 'child-lock\n' >cascade-local/Cargo.lock
printf 'child-group\n' >cascade-local/child-group.txt
printf 'child-global-toolchain\n' >cascade-local/child-global-toolchain.txt
assert "mise -C cascade-local task info child --json | jq -r '.sources[] | select(endswith(\"/Cargo.lock\"))'" "$PWD/cascade-local/Cargo.lock"
assert "mise -C cascade-local task info child --json | jq -r '.sources[] | select(endswith(\"/toolchain.txt\"))'" "$PWD/toolchain.txt"
assert "mise -C cascade-local task info child --json | jq -r '.sources[] | select(endswith(\"/child-group.txt\"))'" "$PWD/cascade-local/child-group.txt"
assert "mise -C cascade-local task info child --json | jq -r '.sources[] | select(endswith(\"/child-global-toolchain.txt\"))'" "$PWD/cascade-local/child-global-toolchain.txt"
assert "mise -C cascade-local task info child --json | jq -r '.sources[] | select(endswith(\"/global-toolchain.txt\"))'" ""
assert "mise -C cascade-local task info child --json | jq -r '.sources[] | select(endswith(\"/workspace.txt\"))'" "$PWD/workspace.txt"

# cascade = false stops both inherited input fields.
mkdir -p cascade-stopped
cat <<'EOF' >cascade-stopped/mise.toml
[task_config]
cascade = false

[tasks.child]
run = "true"
sources = ["@group:workspace-lock"]
outputs = []
EOF
assert_fail_contains "mise -C cascade-stopped task info child" 'undefined input group "workspace-lock"'

# 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"'
