# https://taskfile.dev

version: '3'

tasks:
  core:info:
    silent: true
    cmds:
      - |
        # Color codes
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        BLUE='\033[0;34m'
        BOLD='\033[1m'
        NC='\033[0m' # No Color

        echo -e "${BOLD}Development:${NC}"
        echo -e "  ${GREEN}task install${NC}                     ${YELLOW}i${NC}         Clean install (frozen lockfile, no scripts)"
        echo -e "  ${GREEN}task install:quick${NC}               ${YELLOW}iq${NC}        Quick install (allows lockfile updates)"
        echo -e "  ${GREEN}task build${NC}                       ${YELLOW}b${NC}         Build the project"
        echo -e "  ${GREEN}task test${NC}                        ${YELLOW}t${NC}         Run unit tests"
        echo -e "  ${GREEN}task test:integration${NC}            ${YELLOW}ti${NC}        Run integration tests"
        echo -e "  ${GREEN}task test:coverage${NC}               ${YELLOW}cov${NC}       Run tests with coverage                  FILES=\"...\" or DIFF=true"
        echo -e "  ${GREEN}task test:coverage:report${NC}        ${YELLOW}covr${NC}      Show coverage report with low files      THRESHOLD=80 (default)"
        echo -e "  ${GREEN}task lint${NC}                        ${YELLOW}l${NC}         Run linter                               FILES=\"...\""
        echo -e "  ${GREEN}task lint:fix${NC}                    ${YELLOW}lf${NC}        Run linter with auto-fix                 FILES=\"...\""
        echo -e "  ${GREEN}task dev${NC}                         ${YELLOW}d${NC}         Run in development mode"
        echo ""

  install:
    desc: Clean install dependencies (removes caches, uses frozen lockfile, ignores scripts)
    aliases: [i]
    silent: true
    cmds:
      - |
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        NC='\033[0m'

        # Clean local caches first for reproducible builds
        # See: https://www.lekman.com/blog/2025/11/securing-your-javascript-dependencies-what-every-organisation-needs-to-know/

        # Remove node_modules
        if [ -d "node_modules" ]; then
          rm -rf node_modules
          echo -e "${GREEN}✓${NC} Removed node_modules/"
        fi

        # Remove bun's local cache
        if [ -d ".bun" ]; then
          rm -rf .bun
          echo -e "${GREEN}✓${NC} Removed .bun/"
        fi

        # Remove SST cache (serverless framework)
        if [ -d ".sst" ]; then
          rm -rf .sst
          echo -e "${GREEN}✓${NC} Removed .sst/"
        fi

        # Remove SST output directory
        if [ -d ".open-next" ]; then
          rm -rf .open-next
          echo -e "${GREEN}✓${NC} Removed .open-next/"
        fi

        # Secure install with frozen lockfile and no lifecycle scripts
        # --frozen-lockfile: Ensures exact versions from bun.lockb (fails if lockfile needs update)
        # --ignore-scripts: Prevents execution of potentially malicious postinstall scripts
        echo -e "${GREEN}▶${NC} Installing dependencies (frozen lockfile, no scripts)..."
        bun install --frozen-lockfile --ignore-scripts

        echo -e "${GREEN}✓${NC} Dependencies installed securely"

  install:quick:
    desc: Quick install (no clean, updates lockfile if needed)
    aliases: [iq]
    silent: true
    cmds:
      - bun install

  build:
    desc: Build the project
    aliases: [b]
    silent: true
    cmds:
      - bun run build
    sources:
      - src/**/*.ts
    generates:
      - dist/**/*.js

  rebuild:
    aliases: [rb]
    desc: First cleans project build artifacts, logs, and temporary files, then rebuilds the project
    silent: true
    cmds:
      - task clean
      - task install
      - task build

  clean:
    desc: Clean build artifacts, logs, and temporary files (use venv=true to also delete venv)
    aliases: [c]
    silent: true
    vars:
      DELETE_VENV: '{{.venv | default "false"}}'
    cmds:
      - |
        # Color codes
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        NC='\033[0m'

        # Get repository root
        REPO_ROOT=$(task git:repo:root)

        # Clean build artifacts and dependency caches
        rm -rf dist
        rm -rf node_modules
        rm -rf .bun
        rm -rf .sst
        rm -rf .open-next
        echo -e "${GREEN}✓${NC} Removed dist/, node_modules/, .bun/, .sst/, .open-next/"

        # Clean .logs contents (keep the directory)
        if [ -d .logs ]; then
          find .logs -mindepth 1 -delete
          echo -e "${GREEN}✓${NC} Cleaned .logs/ contents"
        fi

        # Delete *.local.md files in root folder only (not subfolders)
        # EXCEPT: .bugs.local.md and *.report.local.md
        LOCAL_MD_COUNT=$(find . -maxdepth 1 -name "*.local.md" -type f ! -name ".bugs.local.md" ! -name "*.report.local.md" | wc -l | tr -d ' ')
        if [ "$LOCAL_MD_COUNT" -gt 0 ]; then
          find . -maxdepth 1 -name "*.local.md" -type f ! -name ".bugs.local.md" ! -name "*.report.local.md" -delete
          echo -e "${GREEN}✓${NC} Deleted $LOCAL_MD_COUNT *.local.md file(s) from root (preserved .bugs.local.md and *.report.local.md)"
        fi

        # Delete *.txt files in root folder only (not subfolders)
        TXT_COUNT=$(find . -maxdepth 1 -name "*.txt" -type f | wc -l | tr -d ' ')
        if [ "$TXT_COUNT" -gt 0 ]; then
          find . -maxdepth 1 -name "*.txt" -type f -delete
          echo -e "${GREEN}✓${NC} Deleted $TXT_COUNT *.txt file(s) from root"
        fi

        # Delete *.log files in root folder only (not subfolders)
        LOG_COUNT=$(find . -maxdepth 1 -name "*.log" -type f | wc -l | tr -d ' ')
        if [ "$LOG_COUNT" -gt 0 ]; then
          find . -maxdepth 1 -name "*.log" -type f -delete
          echo -e "${GREEN}✓${NC} Deleted $LOG_COUNT *.log file(s) from root"
        fi

        # Delete TypeScript build cache files (recursive)
        TSBUILDINFO_COUNT=$(find . -name "*.tsbuildinfo" -type f | wc -l | tr -d ' ')
        if [ "$TSBUILDINFO_COUNT" -gt 0 ]; then
          find . -name "*.tsbuildinfo" -type f -delete
          echo -e "${GREEN}✓${NC} Deleted $TSBUILDINFO_COUNT *.tsbuildinfo file(s)"
        fi

        # Delete .DS_Store files (macOS metadata, recursive)
        DS_STORE_COUNT=$(find . -name ".DS_Store" -type f | wc -l | tr -d ' ')
        if [ "$DS_STORE_COUNT" -gt 0 ]; then
          find . -name ".DS_Store" -type f -delete
          echo -e "${GREEN}✓${NC} Deleted $DS_STORE_COUNT .DS_Store file(s)"
        fi

        # Optionally delete venv
        {{if eq .DELETE_VENV "true"}}
        if [ -d venv ]; then
          rm -rf venv
          echo -e "${GREEN}✓${NC} Deleted venv/"
        fi
        {{end}}

        # Delete all git worktrees (except main worktree)
        WORKTREE_COUNT=$(git worktree list --porcelain | grep -c "^worktree " || echo 0)
        # Subtract 1 for the main worktree
        if [ "$WORKTREE_COUNT" -gt 1 ]; then
          REMOVED_COUNT=0
          git worktree list --porcelain | grep "^worktree " | cut -d' ' -f2 | while read -r worktree_path; do
            # Skip main worktree (current directory)
            if [ "$worktree_path" != "$REPO_ROOT" ] && [ "$worktree_path" != "." ]; then
              git worktree remove "$worktree_path" --force 2>/dev/null || true
              REMOVED_COUNT=$((REMOVED_COUNT + 1))
            fi
          done

          # Prune stale worktree administrative files
          git worktree prune 2>/dev/null || true

          ACTUAL_COUNT=$((WORKTREE_COUNT - 1))
          echo -e "${GREEN}✓${NC} Removed $ACTUAL_COUNT git worktree(s)"
        fi

  dev:
    desc: Run in development mode
    aliases: [d]
    silent: true
    cmds:
      - bun run dev

  lint:
    desc: Run linter
    aliases: [l]
    silent: true
    vars:
      FILE_PATTERN: '{{.FILES | default "."}}'
    deps:
      - task: yaml:lint
        vars: { FILES: '{{.FILES}}' }
      - task: markdown:lint
        vars: { FILES: '{{.FILES}}' }
    cmds:
      - |
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        RED='\033[0;31m'
        NC='\033[0m'

        # Run typecheck only if FILES not specified (needs full project)
        {{if not .FILES}}
        task typecheck
        {{end}}

        # Detect ESLint
        HAS_ESLINT=false
        if [ -f "eslint.config.js" ] || [ -f "eslint.config.mjs" ] || [ -f "eslint.config.cjs" ] || \
           [ -f ".eslintrc.js" ] || [ -f ".eslintrc.cjs" ] || [ -f ".eslintrc.json" ] || [ -f ".eslintrc" ] || \
           grep -q '"eslintConfig"' package.json 2>/dev/null; then
          HAS_ESLINT=true
        fi

        # Detect Biome
        HAS_BIOME=false
        if [ -f "biome.json" ] || [ -f "biome.jsonc" ]; then
          HAS_BIOME=true
        fi

        # Track overall exit code
        FINAL_EXIT=0

        # Run ESLint - exits 0 for warnings, 1 for errors
        if [ "$HAS_ESLINT" = true ]; then
          echo -e "${GREEN}▶${NC} Running ESLint..."
          bunx eslint {{.FILE_PATTERN}} || FINAL_EXIT=$?
        fi

        # Run Biome lint
        if [ "$HAS_BIOME" = true ]; then
          echo -e "${GREEN}▶${NC} Running Biome lint..."
          bunx biome lint {{.FILE_PATTERN}} || FINAL_EXIT=$?
        fi

        if [ "$HAS_ESLINT" = false ] && [ "$HAS_BIOME" = false ]; then
          echo -e "${YELLOW}⚠${NC} No linter configuration found (ESLint or Biome)"
        fi

        exit $FINAL_EXIT

  lint:fix:
    desc: Run linter with auto-fix
    aliases: [lf]
    silent: true
    vars:
      FILE_PATTERN: '{{.FILES | default "."}}'
    deps:
      - task: yaml:lint:fix
        vars: { FILES: '{{.FILES}}' }
      - task: markdown:lint:fix
        vars: { FILES: '{{.FILES}}' }
    cmds:
      - |
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        NC='\033[0m'

        # Detect ESLint
        HAS_ESLINT=false
        if [ -f "eslint.config.js" ] || [ -f "eslint.config.mjs" ] || [ -f "eslint.config.cjs" ] || \
           [ -f ".eslintrc.js" ] || [ -f ".eslintrc.cjs" ] || [ -f ".eslintrc.json" ] || [ -f ".eslintrc" ] || \
           grep -q '"eslintConfig"' package.json 2>/dev/null; then
          HAS_ESLINT=true
        fi

        # Detect Biome
        HAS_BIOME=false
        if [ -f "biome.json" ] || [ -f "biome.jsonc" ]; then
          HAS_BIOME=true
        fi

        # Detect Prettier
        HAS_PRETTIER=false
        if [ -f ".prettierrc" ] || [ -f ".prettierrc.json" ] || [ -f ".prettierrc.js" ] || \
           [ -f ".prettierrc.cjs" ] || [ -f ".prettierrc.mjs" ] || [ -f "prettier.config.js" ] || \
           [ -f "prettier.config.cjs" ] || [ -f "prettier.config.mjs" ] || \
           grep -q '"prettier"' package.json 2>/dev/null; then
          HAS_PRETTIER=true
        fi

        # Run linters and formatters
        if [ "$HAS_ESLINT" = true ]; then
          echo -e "${GREEN}▶${NC} Running ESLint with auto-fix..."
          bunx eslint --fix {{.FILE_PATTERN}}
        fi

        if [ "$HAS_BIOME" = true ]; then
          echo -e "${GREEN}▶${NC} Running Biome lint with auto-fix..."
          bunx biome lint --write {{.FILE_PATTERN}}
        fi

        if [ "$HAS_PRETTIER" = true ]; then
          echo -e "${GREEN}▶${NC} Running Prettier..."
          bunx prettier --write --log-level=warn {{.FILE_PATTERN}}
        fi

        if [ "$HAS_ESLINT" = false ] && [ "$HAS_BIOME" = false ] && [ "$HAS_PRETTIER" = false ]; then
          echo -e "${YELLOW}⚠${NC} No linter/formatter configuration found (ESLint, Biome, or Prettier)"
        fi

  format:
    desc: Format code
    aliases: [f]
    silent: true
    cmds:
      - |
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        NC='\033[0m'

        # Detect Biome
        HAS_BIOME=false
        if [ -f "biome.json" ] || [ -f "biome.jsonc" ]; then
          HAS_BIOME=true
        fi

        # Detect Prettier
        HAS_PRETTIER=false
        if [ -f ".prettierrc" ] || [ -f ".prettierrc.json" ] || [ -f ".prettierrc.js" ] || \
           [ -f ".prettierrc.cjs" ] || [ -f ".prettierrc.mjs" ] || [ -f "prettier.config.js" ] || \
           [ -f "prettier.config.cjs" ] || [ -f "prettier.config.mjs" ] || \
           grep -q '"prettier"' package.json 2>/dev/null; then
          HAS_PRETTIER=true
        fi

        # Run formatters (Biome first if both exist, as it's faster)
        if [ "$HAS_BIOME" = true ]; then
          echo -e "${GREEN}▶${NC} Running Biome format..."
          bunx biome format --write .
        fi

        if [ "$HAS_PRETTIER" = true ]; then
          echo -e "${GREEN}▶${NC} Running Prettier..."
          bunx prettier --write --log-level=warn .
        fi

        if [ "$HAS_BIOME" = false ] && [ "$HAS_PRETTIER" = false ]; then
          echo -e "${YELLOW}⚠${NC} No formatter configuration found (Biome or Prettier)"
        fi

  typecheck:
    desc: Type check TypeScript
    aliases: [tc]
    silent: true
    cmds:
      - bun run typecheck

  test:
    desc: Run unit tests (excludes integration tests)
    aliases: [t]
    silent: true
    cmds:
      - |
        source "$(task op:export)"
        bun run test

  test:integration:
    desc: Run integration tests (tests that interact with real external systems)
    aliases: [ti]
    silent: true
    cmds:
      - |
        source "$(task op:export)"
        bun run test:integration

  test:coverage:
    desc: Run tests with coverage (use FILES="pattern" or DIFF=true for staged files)
    aliases: [cov]
    silent: true
    vars:
      FILE_PATTERN: '{{.FILES | default ""}}'
      DIFF_MODE: '{{.DIFF | default "false"}}'
    cmds:
      - |
        # Export 1Password secrets
        source "$(task op:export)"

        # Build file list based on mode
        EXIT_CODE=0

        if [ "{{.DIFF_MODE}}" = "true" ]; then
          FILE_LIST=$(git diff --staged --name-only --diff-filter=d '*.ts' '*.tsx' | tr '\n' ' ')
          if [ -z "$FILE_LIST" ]; then
            echo "ℹ️  No TypeScript files staged"
            exit 0
          fi
          export FILES="$FILE_LIST"
        elif [ -n "{{.FILE_PATTERN}}" ]; then
          export FILES="{{.FILE_PATTERN}}"
        fi

        # Always use package.json script (supports FILES env var)
        bun run test:coverage || EXIT_CODE=$?

        # Always show report, then exit with original exit code
        task test:coverage:report
        exit $EXIT_CODE

  test:coverage:report:
    desc: Display coverage report summary from existing test results
    aliases: [covr]
    silent: true
    vars:
      JUNIT_FILE: '.logs/test-results/junit.xml'
      LCOV_FILE: '.logs/coverage/lcov.info'
      THRESHOLD: '{{.THRESHOLD | default "80"}}'
    cmds:
      - |
        # Color codes
        RED='\033[0;31m'
        YELLOW='\033[0;33m'
        GREEN='\033[0;32m'
        NC='\033[0m'

        # Ensure .tmp directory exists for temp files
        mkdir -p .tmp

        # Display coverage summary and output file locations
        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "📊 Coverage Report"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

        # Show lcov summary if available
        if [ -f "{{.LCOV_FILE}}" ]; then
          if command -v lcov &> /dev/null; then
            lcov --summary "{{.LCOV_FILE}}" 2>/dev/null || cat "{{.LCOV_FILE}}" | grep -E "^(SF|LF|LH|end_of_record)" | head -20
          else
            echo "📄 LCOV report: {{.LCOV_FILE}}"
            # Parse basic coverage stats from lcov
            LINES_HIT=$(grep "^LH:" "{{.LCOV_FILE}}" 2>/dev/null | awk -F: '{sum+=$2} END {print sum}')
            LINES_TOTAL=$(grep "^LF:" "{{.LCOV_FILE}}" 2>/dev/null | awk -F: '{sum+=$2} END {print sum}')

            if [ -n "$LINES_HIT" ] && [ -n "$LINES_TOTAL" ] && [ "$LINES_TOTAL" -gt 0 ]; then
              COVERAGE_PCT=$(awk "BEGIN {printf \"%.1f\", ($LINES_HIT/$LINES_TOTAL)*100}")
              echo "   Lines: ${LINES_HIT}/${LINES_TOTAL} (${COVERAGE_PCT}%)"
            fi
          fi

          echo ""
          echo "⚠️  Files Below {{.THRESHOLD}}% Coverage"
          echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

          # Parse lcov.info to find files below threshold
          awk -v threshold="{{.THRESHOLD}}" '
            /^SF:/ {
              file = substr($0, 4)  # Extract filename after "SF:"
              gsub(/^.*\/src\//, "src/", file)  # Shorten path to src/...
            }
            /^LF:/ { lines_found = substr($0, 4) }
            /^LH:/ { lines_hit = substr($0, 4) }
            /^end_of_record/ {
              if (lines_found > 0) {
                coverage = (lines_hit / lines_found) * 100
                if (coverage < threshold) {
                  printf "   %-50s %6.1f%% (%d/%d lines)\n", file, coverage, lines_hit, lines_found
                }
              }
              file = ""; lines_found = 0; lines_hit = 0
            }
          ' "{{.LCOV_FILE}}" | sort -t'%' -k2 -n > .tmp/low_coverage_files.txt

          if [ -s .tmp/low_coverage_files.txt ]; then
            cat .tmp/low_coverage_files.txt
            echo ""
            FILE_COUNT=$(wc -l < .tmp/low_coverage_files.txt | tr -d ' ')
            echo -e "${RED}   $FILE_COUNT file(s) below {{.THRESHOLD}}% coverage${NC}"
          else
            echo -e "${GREEN}   All files meet or exceed {{.THRESHOLD}}% coverage ✓${NC}"
          fi
          rm -f .tmp/low_coverage_files.txt
        else
          echo "⚠️  LCOV file not found: {{.LCOV_FILE}}"
        fi

        echo ""
        echo "📋 Test Results"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

        # Show junit.xml location
        if [ -f "{{.JUNIT_FILE}}" ]; then
          echo "   JUnit XML: {{.JUNIT_FILE}}"
          # Show test summary from junit.xml
          TOTAL_TESTS=$(grep -o 'tests="[0-9]*"' "{{.JUNIT_FILE}}" 2>/dev/null | head -1 | grep -o '[0-9]*' || echo "?")
          FAILURES=$(grep -o 'failures="[0-9]*"' "{{.JUNIT_FILE}}" 2>/dev/null | head -1 | grep -o '[0-9]*' || echo "?")
          echo "   Total: $TOTAL_TESTS tests, $FAILURES failures"
        else
          echo "⚠️  JUnit file not found: {{.JUNIT_FILE}}"
        fi

        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""

  ci:
    desc: Run CI checks (lint, typecheck, test, build)
    silent: true
    deps:
      - lint
      - typecheck
      - test
      - build
