{ pkgs, ... }:

let
  dnsProvider = pkgs."octodns-providers".cloudflare;
  enterRepo = ''
    repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
    cd "$repo_root"
    export CULTROLL_REPO_ROOT="$repo_root"
    export CULTROLL_DEVENV_STATE_ROOT="$repo_root/.devenv/state"
    export CULTROLL_WRANGLER_STATE_ROOT="$CULTROLL_DEVENV_STATE_ROOT/wrangler"
    export CULTROLL_WRANGLER_STATE_V3="$CULTROLL_WRANGLER_STATE_ROOT/v3"
    export CULTROLL_WRANGLER_WORK_ROOT="$CULTROLL_WRANGLER_STATE_ROOT/workspaces"
    export CULTROLL_ROOT_WRANGLER_WORKDIR="$CULTROLL_WRANGLER_WORK_ROOT/root"
    export CULTROLL_SCRAPER_WRANGLER_WORKDIR="$CULTROLL_WRANGLER_WORK_ROOT/scraper"
    export CULTROLL_LOG_ROOT="$CULTROLL_DEVENV_STATE_ROOT/logs"
    export CULTROLL_DEVENV_TMP_ROOT="$CULTROLL_DEVENV_STATE_ROOT/tmp"
    export CULTROLL_XDG_ROOT="$CULTROLL_DEVENV_STATE_ROOT/xdg"
    export npm_config_cache="$CULTROLL_DEVENV_STATE_ROOT/npm"
    export TMPDIR="$CULTROLL_DEVENV_TMP_ROOT"
    export XDG_CACHE_HOME="$CULTROLL_XDG_ROOT/cache"
    export XDG_CONFIG_HOME="$CULTROLL_XDG_ROOT/config"
    export XDG_STATE_HOME="$CULTROLL_XDG_ROOT/state"
    export PATH="$PATH:$repo_root/node_modules/.bin"

    cultroll_resolve_wrangler_bin() {
      repo_wrangler="$repo_root/node_modules/.bin/wrangler"
      if [ -x "$repo_wrangler" ]; then
        printf '%s\n' "$repo_wrangler"
        return 0
      fi

      fallback_wrangler="$(command -v wrangler 2>/dev/null || true)"
      if [ -n "$fallback_wrangler" ]; then
        printf '%s\n' "$fallback_wrangler"
        return 0
      fi

      echo "Wrangler is not available. Run cultroll-install." >&2
      return 1
    }

    cultroll_migrate_state_dir() {
      if [ "$#" -ne 3 ]; then
        echo "usage: cultroll_migrate_state_dir <source> <target> <label>" >&2
        return 1
      fi

      source_dir="$1"
      target_dir="$2"
      label="$3"
      [ -e "$source_dir" ] || return 0

      mkdir -p "$(dirname "$target_dir")"
      echo "Migrating $label to $target_dir"
      if [ -e "$target_dir" ]; then
        mkdir -p "$target_dir"
        cp -a "$source_dir"/. "$target_dir"/
        rm -rf "$source_dir"
      else
        mv "$source_dir" "$target_dir"
      fi
    }

    cultroll_migrate_state_dir "$repo_root/.wrangler/state" "$CULTROLL_WRANGLER_STATE_ROOT" "Wrangler local state"
    cultroll_migrate_state_dir "$repo_root/logs" "$CULTROLL_LOG_ROOT" "CultRoll local logs"
    cultroll_migrate_state_dir "$repo_root/.npm" "$npm_config_cache" "npm cache"

    cultroll_link_state_dir() {
      if [ "$#" -ne 3 ]; then
        echo "usage: cultroll_link_state_dir <repo-path> <target> <label>" >&2
        return 1
      fi

      repo_path="$1"
      target_dir="$2"
      label="$3"
      mkdir -p "$(dirname "$repo_path")" "$target_dir"

      if [ -L "$repo_path" ]; then
        current_target="$(readlink -f "$repo_path" 2>/dev/null || true)"
        if [ "$current_target" = "$target_dir" ]; then
          return 0
        fi
        rm -f "$repo_path"
      elif [ -e "$repo_path" ]; then
        echo "Migrating $label to $target_dir"
        cp -a "$repo_path"/. "$target_dir"/ 2>/dev/null || true
        rm -rf "$repo_path"
      fi

      ln -s "$target_dir" "$repo_path"
    }

    cultroll_prepare_wrangler_workspace() {
      if [ "$#" -ne 1 ]; then
        echo "usage: cultroll_prepare_wrangler_workspace <workspace>" >&2
        return 1
      fi

      workspace="$1"
      mkdir -p "$workspace"
      rm -rf "$workspace/tmp"
      mkdir -p "$workspace/tmp"
    }

    cultroll_collect_child_pids() {
      if [ "$#" -ne 1 ]; then
        echo "usage: cultroll_collect_child_pids <pid>" >&2
        return 1
      fi

      parent_pid="$1"
      child_pids="$(ps -o pid= --ppid "$parent_pid" 2>/dev/null | tr -s ' ' '\n' | sed '/^$/d')"
      for child_pid in $child_pids; do
        printf '%s\n' "$child_pid"
        cultroll_collect_child_pids "$child_pid"
      done
    }

    cultroll_kill_process_tree() {
      if [ "$#" -ne 1 ]; then
        echo "usage: cultroll_kill_process_tree <pid>" >&2
        return 1
      fi

      root_pid="$1"
      [ -n "$root_pid" ] || return 0

      child_pids="$(cultroll_collect_child_pids "$root_pid" | sort -rn | uniq)"
      for pid in $child_pids $root_pid; do
        kill "$pid" 2>/dev/null || true
      done

      sleep 1
      for pid in $child_pids $root_pid; do
        if kill -0 "$pid" 2>/dev/null; then
          kill -9 "$pid" 2>/dev/null || true
        fi
      done
    }

    mkdir -p \
      "$CULTROLL_DEVENV_STATE_ROOT" \
      "$CULTROLL_WRANGLER_STATE_ROOT" \
      "$CULTROLL_WRANGLER_STATE_V3" \
      "$CULTROLL_WRANGLER_WORK_ROOT" \
      "$CULTROLL_LOG_ROOT" \
      "$CULTROLL_DEVENV_TMP_ROOT" \
      "$XDG_CACHE_HOME" \
      "$XDG_CONFIG_HOME" \
      "$XDG_CONFIG_HOME/.wrangler" \
      "$XDG_CONFIG_HOME/.wrangler/logs" \
      "$XDG_CONFIG_HOME/process-compose" \
      "$XDG_STATE_HOME" \
      "$npm_config_cache"
    cultroll_link_state_dir "$repo_root/.wrangler" "$CULTROLL_ROOT_WRANGLER_WORKDIR" "repo Wrangler workspace"
    cultroll_link_state_dir "$repo_root/functions/cultroll-scraper/.wrangler" "$CULTROLL_SCRAPER_WRANGLER_WORKDIR" "scraper Wrangler workspace"
    export CULTROLL_WRANGLER_BIN="$(cultroll_resolve_wrangler_bin)"

    cultroll_ensure_port_free() {
      if [ "$#" -ne 3 ]; then
        echo "usage: cultroll_ensure_port_free <port> <expected-cwd> <cmd-match>" >&2
        return 1
      fi

      port="$1"
      expected_cwd="$2"
      cmd_match="$3"
      listener_output="$(ss -lHtnp "( sport = :$port )" 2>/dev/null || true)"
      listener_pids="$(printf '%s\n' "$listener_output" | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | sort -u)"

      for pid in $listener_pids; do
        [ -r "/proc/$pid/cmdline" ] || continue
        proc_cwd="$(readlink -f "/proc/$pid/cwd" 2>/dev/null || true)"
        cmdline="$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true)"

        if [ "$proc_cwd" = "$expected_cwd" ] && { printf '%s' "$cmdline" | grep -F -- "$cmd_match" >/dev/null || printf '%s' "$cmdline" | grep -F -- "workerd serve" >/dev/null; }; then
          echo "Stopping stale repo listener on :$port (pid $pid)"
          kill "$pid"
        fi
      done

      for _ in $(seq 1 5); do
        listener_output="$(ss -lHtnp "( sport = :$port )" 2>/dev/null || true)"
        [ -z "$listener_output" ] && return 0
        sleep 1
      done

      listener_output="$(ss -lHtnp "( sport = :$port )" 2>/dev/null || true)"
      listener_pids="$(printf '%s\n' "$listener_output" | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | sort -u)"
      for pid in $listener_pids; do
        [ -r "/proc/$pid/cmdline" ] || continue
        proc_cwd="$(readlink -f "/proc/$pid/cwd" 2>/dev/null || true)"
        cmdline="$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true)"

        if [ "$proc_cwd" = "$expected_cwd" ] && { printf '%s' "$cmdline" | grep -F -- "$cmd_match" >/dev/null || printf '%s' "$cmdline" | grep -F -- "workerd serve" >/dev/null; }; then
          echo "Force-stopping stubborn repo listener on :$port (pid $pid)"
          kill -9 "$pid"
        fi
      done

      for _ in $(seq 1 5); do
        listener_output="$(ss -lHtnp "( sport = :$port )" 2>/dev/null || true)"
        [ -z "$listener_output" ] && return 0
        sleep 1
      done

      listener_output="$(ss -lHtnp "( sport = :$port )" 2>/dev/null || true)"
      if [ -n "$listener_output" ]; then
        echo "Port $port is still busy; active listeners:" >&2
        printf '%s\n' "$listener_output" >&2
        return 1
      fi
    }
  '';
  exportAllowedHosts = ''
    runtime_allowed_hosts="localhost,127.0.0.1,::1"
    if [ -n "''${HOSTNAME:-}" ]; then
      runtime_allowed_hosts="$runtime_allowed_hosts,''${HOSTNAME}"
    fi
    short_hostname="$(hostname -s 2>/dev/null || true)"
    if [ -n "$short_hostname" ]; then
      runtime_allowed_hosts="$runtime_allowed_hosts,$short_hostname"
    fi
    hostname_value="$(hostname 2>/dev/null || true)"
    if [ -n "$hostname_value" ]; then
      runtime_allowed_hosts="$runtime_allowed_hosts,$hostname_value"
    fi
    export CULTROLL_ALLOWED_HOSTS="''${CULTROLL_ALLOWED_HOSTS:+$CULTROLL_ALLOWED_HOSTS,}$runtime_allowed_hosts"
  '';
in
{
  dotenv.enable = true;

  packages = with pkgs; [
    curl
    git
    gnumake
    iproute2
    jq
    nodejs_22
    octodns
    openssl
    pkg-config
    python3
    vips
    wrangler
    dnsProvider
  ];

  env = {
    ASTRO_TELEMETRY_DISABLED = "1";
    HOST = "0.0.0.0";
    CULTROLL_APP_PORT = "4324";
    CULTROLL_ADMIN_PORT = "4328";
    CULTROLL_SCRAPER_PORT = "8787";
  };

  scripts = {
    "cultroll-install" = {
      exec = enterRepo + ''
        npm ci
      '';
      description = "Install the Node dependencies pinned by package-lock.json";
    };

    "cultroll-dev" = {
      exec = enterRepo + ''
        set -euo pipefail
        port="''${CULTROLL_APP_PORT:-4324}"
        cultroll_ensure_port_free "$port" "$repo_root" "astro.config.cultroll.mjs"
        ${exportAllowedHosts}
        HOST="''${HOST:-0.0.0.0}" \
        PORT="$port" \
        npm run dev:cultroll
      '';
      description = "Run the public app locally with Cloudflare Pages bindings";
    };

    "cultroll-admin-dev" = {
      exec = enterRepo + ''
        set -euo pipefail
        port="''${CULTROLL_ADMIN_PORT:-4328}"
        cultroll_ensure_port_free "$port" "$repo_root" "astro.config.cultroll-admin.mjs"
        ${exportAllowedHosts}
        HOST="''${HOST:-0.0.0.0}" \
        PORT="$port" \
        npm run dev:cultroll-admin
      '';
      description = "Run the admin app locally with Cloudflare Pages bindings";
    };

    "cultroll-scraper-dev" = {
      exec = enterRepo + ''
        set -euo pipefail
        host="''${HOST:-0.0.0.0}"
        port="''${CULTROLL_SCRAPER_PORT:-8787}"
        cultroll_ensure_port_free "$port" "$repo_root/functions/cultroll-scraper" "wrangler"
        cultroll_prepare_wrangler_workspace "$CULTROLL_SCRAPER_WRANGLER_WORKDIR"
        cd functions/cultroll-scraper
        "$CULTROLL_WRANGLER_BIN" dev \
          --local \
          --ip "$host" \
          --port "$port" \
          --persist-to "$CULTROLL_WRANGLER_STATE_ROOT"
      '';
      description = "Run the scraper worker locally with shared D1/Queue state";
    };

    "cultroll-scrape-local" = {
      exec = enterRepo + ''
        set -euo pipefail
        host="''${HOST:-0.0.0.0}"
        port="''${CULTROLL_SCRAPER_PORT:-8787}"
        scraper_url="http://127.0.0.1:$port"
        tmpdir="$TMPDIR"
        mkdir -p "$tmpdir" "$CULTROLL_LOG_ROOT"
        cultroll_ensure_port_free "$port" "$repo_root/functions/cultroll-scraper" "wrangler"
        cultroll_prepare_wrangler_workspace "$CULTROLL_SCRAPER_WRANGLER_WORKDIR"

        shell_tmdb_api_key="''${TMDB_API_KEY:-}"
        shell_scraper_admin_token="''${SCRAPER_ADMIN_TOKEN:-}"
        tmdb_api_key=""
        scraper_admin_token=""
        if [ -f "$repo_root/functions/cultroll-scraper/.dev.vars" ]; then
          set -a
          . "$repo_root/functions/cultroll-scraper/.dev.vars"
          set +a
          tmdb_api_key="''${TMDB_API_KEY:-}"
          scraper_admin_token="''${SCRAPER_ADMIN_TOKEN:-}"
        fi
        tmdb_api_key="''${tmdb_api_key:-$shell_tmdb_api_key}"
        scraper_admin_token="''${scraper_admin_token:-$shell_scraper_admin_token}"

        if [ -z "$scraper_admin_token" ]; then
          scraper_admin_token="$(openssl rand -hex 24)"
          echo "SCRAPER_ADMIN_TOKEN not found in functions/cultroll-scraper/.dev.vars; using an ephemeral token for this run."
        fi

        if [ -z "$tmdb_api_key" ]; then
          echo "TMDB_API_KEY is not configured in functions/cultroll-scraper/.dev.vars or the shell environment; metadata-enricher will be skipped and local data will stay source-only."
        fi

        worker_env_file="$(mktemp -p "$tmpdir" cultroll-scraper-local.XXXXXX.env)"
        cleanup() {
          rm -f "$worker_env_file"
          if [ -n "''${worker_pid:-}" ]; then
            cultroll_kill_process_tree "$worker_pid"
            wait "$worker_pid" 2>/dev/null || true
          fi
        }
        trap cleanup EXIT INT TERM

        {
          printf 'SCRAPER_ADMIN_TOKEN=%s\n' "$scraper_admin_token"
          if [ -n "$tmdb_api_key" ]; then
            printf 'TMDB_API_KEY=%s\n' "$tmdb_api_key"
          fi
        } > "$worker_env_file"

        cultroll-db-local-reset

        (
          cd "$repo_root/functions/cultroll-scraper"
          "$CULTROLL_WRANGLER_BIN" dev \
            --local \
            --ip "$host" \
            --port "$port" \
            --persist-to "$CULTROLL_WRANGLER_STATE_ROOT" \
            --env-file "$worker_env_file" \
            > "$CULTROLL_LOG_ROOT/cultroll-scrape-local.log" 2>&1
        ) &
        worker_pid="$!"

        ready=0
        for _ in $(seq 1 60); do
          if curl -fsS -H "Authorization: Bearer $scraper_admin_token" "$scraper_url/health" >/dev/null; then
            ready=1
            break
          fi
          sleep 1
        done

        if [ "$ready" -ne 1 ]; then
          echo "Scraper worker failed to become ready. Last log lines:"
          tail -n 80 "$CULTROLL_LOG_ROOT/cultroll-scrape-local.log" || true
          exit 1
        fi

        status_json="$(curl -fsS -H "Authorization: Bearer $scraper_admin_token" "$scraper_url/status")"
        enabled_targets="$(printf '%s' "$status_json" | jq -r '.sources[] | select((.enabled // 0) == 1) | .id')"
        if [ -z "$enabled_targets" ]; then
          echo "No enabled source targets found in local D1."
          exit 1
        fi

        echo "Running enabled source targets against $scraper_url"
        for target in $enabled_targets; do
          echo "==> source:$target"
          curl -fsS \
            -X POST \
            -H "Authorization: Bearer $scraper_admin_token" \
            "$scraper_url/run?target=$target&kind=source&sync=1" \
            | jq .
        done

        if [ -n "$tmdb_api_key" ]; then
          pipeline_targets="metadata-enricher chain-posters metadata-dedup"
        else
          pipeline_targets="chain-posters metadata-dedup"
          echo "TMDB_API_KEY is not configured; skipping metadata-enricher and continuing with local poster/dedup pipeline stages."
        fi

        for target in $pipeline_targets; do
          echo "==> pipeline:$target"
          pipeline_request_args=()
          case "$target" in
            metadata-enricher)
              pipeline_request_args=(
                -H "Content-Type: application/json"
                --data '{"limit":200}'
              )
              ;;
            chain-posters)
              pipeline_request_args=(
                -H "Content-Type: application/json"
                --data '{"limit":200}'
              )
              ;;
          esac
          curl -fsS \
            -X POST \
            -H "Authorization: Bearer $scraper_admin_token" \
            "''${pipeline_request_args[@]}" \
            "$scraper_url/run?target=$target&kind=pipeline&sync=1" \
            | jq .
        done

        summary_json="$("$CULTROLL_WRANGLER_BIN" --config "$repo_root/wrangler.cultroll.toml" d1 execute cultroll-db --local --persist-to "$CULTROLL_WRANGLER_STATE_ROOT" --json --command "SELECT (SELECT COUNT(*) FROM titles) AS titles, (SELECT COUNT(*) FROM showtimes) AS showtimes, (SELECT COUNT(*) FROM scraper_runs) AS scraper_runs, (SELECT COUNT(*) FROM titles WHERE tmdb_id IS NOT NULL OR imdb_id IS NOT NULL) AS titles_with_ids, (SELECT COUNT(*) FROM titles WHERE poster_url IS NOT NULL) AS titles_with_posters, (SELECT COUNT(*) FROM titles WHERE synopsis_en IS NOT NULL OR synopsis_ar IS NOT NULL) AS titles_with_synopsis")"
        printf '%s\n' "$summary_json" | jq -r '.. | objects | select(has("titles") and has("showtimes") and has("scraper_runs")) | "Local D1 summary: \(.titles) titles, \(.showtimes) showtimes, \(.scraper_runs) scraper runs"'
        printf '%s\n' "$summary_json" | jq -r '.. | objects | select(has("titles_with_ids") and has("titles_with_posters") and has("titles_with_synopsis")) | "Metadata coverage: \(.titles_with_ids) titles with TMDB/IMDb IDs, \(.titles_with_posters) with posters, \(.titles_with_synopsis) with synopsis"'
      '';
      description = "Reset local D1, run all enabled scraper targets, and populate local data";
    };

    "cultroll-build" = {
      exec = enterRepo + ''
        npm run build
      '';
      description = "Build both Astro surfaces";
    };

    "cultroll-db-local-schema" = {
      exec = enterRepo + ''
        "$CULTROLL_WRANGLER_BIN" --config "$repo_root/wrangler.cultroll.toml" d1 execute cultroll-db --local --persist-to "$CULTROLL_WRANGLER_STATE_ROOT" --file "$repo_root/src/sites/cultroll/lib/schema.sql"
      '';
      description = "Apply the D1 schema to the local database";
    };

    "cultroll-db-local-seed" = {
      exec = enterRepo + ''
        "$CULTROLL_WRANGLER_BIN" --config "$repo_root/wrangler.cultroll.toml" d1 execute cultroll-db --local --persist-to "$CULTROLL_WRANGLER_STATE_ROOT" --file "$repo_root/src/sites/cultroll/lib/seed.sql"
      '';
      description = "Seed the local D1 database";
    };

    "cultroll-db-local-reset" = {
      exec = enterRepo + ''
        rm -rf \
          "$CULTROLL_WRANGLER_STATE_ROOT/d1" \
          "$CULTROLL_WRANGLER_STATE_ROOT/v3/d1"
        cultroll-db-local-schema
        cultroll-db-local-seed
      '';
      description = "Recreate the local D1 schema and reseed it";
    };

    "cultroll-db-remote-schema" = {
      exec = enterRepo + ''
        "$CULTROLL_WRANGLER_BIN" --config "$repo_root/wrangler.cultroll.toml" d1 execute cultroll-db --remote --file "$repo_root/src/sites/cultroll/lib/schema.sql"
      '';
      description = "Apply the D1 schema against the remote database";
    };

    "cultroll-db-remote-seed" = {
      exec = enterRepo + ''
        "$CULTROLL_WRANGLER_BIN" --config "$repo_root/wrangler.cultroll.toml" d1 execute cultroll-db --remote --file "$repo_root/src/sites/cultroll/lib/seed.sql"
      '';
      description = "Seed the remote D1 database";
    };

    "cultroll-db-remote-sync" = {
      exec = enterRepo + ''
        cultroll-db-remote-schema
        cultroll-db-remote-seed
      '';
      description = "Apply schema and seed against the remote D1 database";
    };

    "cultroll-dns-plan" = {
      exec = enterRepo + ''
        octodns-sync --config-file dns/config.yaml
      '';
      description = "Preview DNS changes against the configured Cloudflare account";
    };

    "cultroll-dns-apply" = {
      exec = enterRepo + ''
        octodns-sync --config-file dns/config.yaml --doit
      '';
      description = "Apply DNS changes to the configured Cloudflare account";
    };
  };

  tasks = {
    "cultroll:scrape-local".exec = "cultroll-scrape-local";
  };

  processes = {
    public.exec = "cultroll-dev";
    admin.exec = "cultroll-admin-dev";
  };

  enterShell = enterRepo + ''
    echo "CultRoll devenv ready."
    echo "Bootstrap: cultroll-install"
    echo "Host: $HOST"
    echo "State root: $CULTROLL_DEVENV_STATE_ROOT"
    echo "Allowed hosts: ''${CULTROLL_ALLOWED_HOSTS:-auto from HOSTNAME + loopback}"
    echo "Public URL:  http://127.0.0.1:$CULTROLL_APP_PORT"
    echo "Admin URL:   http://127.0.0.1:$CULTROLL_ADMIN_PORT/admin"
    echo "Scraper URL: http://127.0.0.1:$CULTROLL_SCRAPER_PORT"
    echo "Apps: cultroll-dev | cultroll-admin-dev | devenv up"
    echo "Scraper: cultroll-scraper-dev | devenv tasks run cultroll:scrape-local"
    echo "Build: cultroll-build"
    echo "D1: cultroll-db-local-reset | cultroll-db-remote-sync"
    echo "DNS: cultroll-dns-plan | cultroll-dns-apply"
    echo "Copy .env.example -> .env for repo ops credentials."
    echo "Copy .dev.vars.example -> .dev.vars for Pages dev runtime bindings."
    echo "Copy functions/cultroll-scraper/.dev.vars.example -> functions/cultroll-scraper/.dev.vars for scraper secrets."
  '';

  enterTest = enterRepo + ''
    node --version
    npm --version
    "$CULTROLL_WRANGLER_BIN" --version
    octodns-sync --version
    npm test
    devenv tasks list | grep 'cultroll:scrape-local'
  '';
}
