export const BOT_VERSION: string = (
  (await Bun.file(new URL("../../package.json", import.meta.url)).json()) as { version: string }
).version;
export const INSTANCE_ID: string = crypto.randomUUID().slice(0, 8);

export const BOT_ENV: string = (() => {
  if (process.env.ECS_CONTAINER_METADATA_URI || process.env.AWS_EXECUTION_ENV) return "aws";
  if (process.env.AWS_REGION && process.env.AWS_DEFAULT_REGION) return "aws";
  return "local";
})();

export const SAFETY = {
  MAX_FILES_CHANGED: 10,
  MAX_DIFF_LINES: 500,
  MAX_FILES_DELETED: 3,
  FORBIDDEN_PATHS: [
    ".github/workflows/",
    ".gitlab-ci.yml",
    "Jenkinsfile",
    ".circleci/",
    ".travis.yml",
    "azure-pipelines.yml",
    "bitbucket-pipelines.yml",
  ],
  LOCK_FILES: ["bun.lock", "bun.lockb", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"],
} as const;

export const QUEUE = {
  MAX_RETRIES: 2,
  /**
   * Must sit above the full sequential agent path so the job timer only fires on a
   * true hang, never on a legit long-but-progressing job — `executeWithTimeout` is a
   * soft Promise.race that abandons + retries while the original run keeps going, so
   * cutting off a followup mid-flight risks duplicate PRs / git races.
   * 100 min = triage (20) + initial fix (45) + followup (20) + ~15 buffer for sync/build/push.
   */
  JOB_TIMEOUT_MS: 100 * 60 * 1000,
  CONCURRENCY: 1,
} as const;

export const SHUTDOWN = {
  /**
   * Max wait for the in-flight job to finish on SIGTERM before aborting it.
   * Constraint: DRAIN_GRACE_MS + CLEANUP_GRACE_MS must be < ECS stop_timeout (120s, its max),
   * since the two are additive on the abort path; 100s + 15s = 115s leaves ~5s margin for the
   * subprocess SIGTERM→SIGKILL escalation.
   */
  DRAIN_GRACE_MS: 100_000,
  /** After aborting, max wait for the job's cleanup (e.g. Jira revert) before exiting. */
  CLEANUP_GRACE_MS: 15_000,
} as const;

export const CLONE = {
  /**
   * Upper bound on the boot prewarm. If clones don't finish in time the bot marks
   * itself ready anyway and falls back to on-demand sync (degraded, not broken).
   * Must sit under the container health-check startPeriod (300s, the ECS maximum)
   * so prewarm resolves before failed /health checks count toward a task kill.
   */
  PREWARM_TIMEOUT_MS: 4 * 60 * 1000,
} as const;

/** Agent run timeouts, shared by both providers (Cursor and Claude). */
export const AGENT = {
  TRIAGE_TIMEOUT_MS: 20 * 60 * 1000,
  INITIAL_TIMEOUT_MS: 45 * 60 * 1000,
  FOLLOWUP_TIMEOUT_MS: 20 * 60 * 1000,
} as const;

/** Base URL for Cursor's hosted background-agent HTTP API (Cursor Cloud only). */
export const CURSOR_CLOUD_BASE_URL = "https://api.cursor.com";

/**
 * Dev-mode defaults. When `NODE_ENV !== "production"`, the bot:
 *   - forces all pollers + the daily-report scheduler off, ignoring their toggles;
 *   - redirects every Slack post to `CHANNEL_ID` and prefixes it with `PREFIX`,
 *     so it is impossible for a local run to message a real channel.
 * Override the channel via `SLACK_DEV_CHANNEL_ID`.
 */
export const DEV = {
  CHANNEL_ID: "C0B19DPH18R",
  PREFIX: "[DEV]",
} as const;

export const POLLER = {
  INTERVAL_MS: 5_000,
  PR_COMMENT_INTERVAL_MS: 30_000,
  SLACK_MENTION_INTERVAL_MS: 30_000,
  PR_MENTION_INTERVAL_MS: 60_000,
  /** Jira poll for @bot + `review-ticket` in comment (card implementation readiness). */
  TICKET_REVIEW_INTERVAL_MS: 30_000,
} as const;

export const JIRA_RECOVERY = {
  /** Min age (since `updated`) for an "In Progress" issue to be treated as orphaned, so we don't race a just-started job. */
  ORPHAN_MIN_AGE_MS: 60_000,
} as const;

/** Jira comment must include this exact substring (case-insensitive) plus @bot mention to run ticket-review readiness. */
export const TICKET_REVIEW_COMMENT_TRIGGER = "review-ticket" as const;

export const SLACK_MENTION = {
  TIMEOUT_MS: 5 * 60 * 1000,
  /** How often to refresh the list of channels the bot is a member of. */
  CHANNEL_REFRESH_MS: 5 * 60 * 1000,
  /** TTL for the resolveUsers in-memory cache. Slack display names rarely change; 1h is plenty. */
  USER_CACHE_TTL_MS: 60 * 60 * 1000,
  /** Hard cap on the rendered "## Thread Context" block in the slack-mention prompt (chars). */
  THREAD_CONTEXT_MAX_CHARS: 6000,
  /** Reaction added when a mention is claimed/enqueued (in-progress). */
  REACTION_CLAIMED: "eyes",
  /** Reaction added when a mention has been answered. */
  REACTION_DONE: "white_check_mark",
  /** Reaction added when a mention failed. */
  REACTION_FAILED: "x",
} as const;

export const ATTACHMENTS = {
  /** Inline images extracted from PR/GitHub comment bodies; also cap for fix/triage Jira attachment downloads. */
  MAX_IMAGES: 5,
  /** Card readiness / ticket-review pipeline only: more screenshots for implementation-readiness assessment. */
  MAX_JIRA_ISSUE_IMAGES: 25,
  MAX_SIZE_BYTES: 5 * 1024 * 1024,
  MD_IMAGE_RE: /!\[[^\]]*\]\((https?:\/\/[^)]+)\)/g,
  HTML_IMG_RE: /<img\b[^>]*\bsrc=["'](https?:\/\/[^"']+)["'][^>]*\/?>/gi,
  VIDEO: {
    MAX_SIZE_BYTES: 100 * 1024 * 1024,
    MAX_DURATION_S: 600,
    MAX_FRAMES: 20,
    MAX_FRAMES_READINESS: 12,
    MAX_VIDEOS_PER_TICKET: 1,
    MAX_VIDEOS_READINESS: 3,
    SCENE_THRESHOLD: 0.12,
    LONGEST_SIDE_PX: 1280,
    JPEG_QSCALE: 4,
    FFMPEG_TIMEOUT_MS: 120_000,
    MIME_TYPES: ["video/mp4", "video/quicktime", "video/webm", "video/x-matroska"] as const,
  },
} as const;

export const MEMORY = {
  MAX_HISTORY_CHARS: 8_000,
  MAX_COMMENT_CHARS: 5000,
  DIR: "memory",
} as const;

export const REPO_SELECTOR = {
  KEYWORD_WEIGHT: 2,
  DESCRIPTION_WORD_WEIGHT: 1,
  MIN_SCORE_THRESHOLD: 1,
} as const;

/**
 * GitHub logins whose PR comments the bot must ignore by default.
 * Consumers: src/utils/config.ts (IGNORED_COMMENT_LOGINS default),
 * src/poller/comment-filter.ts.
 *
 * Users can override wholesale via the IGNORED_COMMENT_LOGINS env var.
 */
export const DEFAULT_IGNORED_COMMENT_LOGINS = ["cursor[bot]", "hu-reviewer[bot]"] as const;

export const SLACK_BOT_USER_ID = "U0AJU6F4RPV" as const;

export const JIRA_FLOW_FIELD_ID = "customfield_10355" as const;

export const BRANCH_PREFIX = "fix" as const;

/** Extracts Jira-style ticket key (e.g. SQDP-3539) from branch name; group 1 is project prefix (e.g. SQDP). */
export const DAILY_REPORT = {
  TICKET_PREFIX_RE: /([A-Z]{2,10})-\d+/,
  STALE_THRESHOLD_MS: 5 * 24 * 60 * 60 * 1000, // 5 days
} as const;

/**
 * Jira project key → tribe name.
 * Empty string means no tribe (DevOps, Cross).
 */
export const SQUAD_TRIBE_MAP: Record<string, string> = {
  SQCY: "People Foundation",
  SQDO: "",
  SQDP: "Talent",
  SQEG: "Time Management",
  SQGZ: "Talent",
  SQJG: "Time Management",
  SQKA: "People Foundation",
  SQOT: "Cross",
  SQOW: "Data",
  SQPD: "Operations & Finance",
  SQPM: "Talent",
  SQRC: "Operations & Finance",
  SQRN: "Communication",
  SQSH: "Communication",
  SQSQ: "Talent",
  SQXS: "",
  SQZB: "Communication",
  SQCC: "Time Management",
  SQKG: "Data",
  SQKM: "Operations & Finance",
  SQWH: "Operations & Finance",
};

/**
 * Tribe name → ordered list of candidate GitHub label strings to try.
 * Handles both shorthand labels (comm, ops) used in web/api repos and
 * grouped labels (Talent & Data, Time & People) used in mobile.
 * The first candidate that exists in the repo's actual labels wins.
 */
export const TRIBE_LABEL_CANDIDATES: Record<string, string[]> = {
  Communication: ["comm", "communication", "Communication"],
  "Operations & Finance": [
    "ops",
    "operations",
    "Operations",
    "ops & finance",
    "operations & finance",
    "Operations & Finance",
  ],
  "Time Management": ["time management", "time & people", "Time & People", "Time Management"],
  "People Foundation": ["people foundation", "time & people", "Time & People", "People Foundation"],
  Talent: ["talent", "talent & data", "Talent & Data", "Talent"],
  Data: ["data", "talent & data", "Talent & Data", "Data"],
  Cross: [],
};

/**
 * Jira project key → Slack usergroup ID (subteam mention `S…`).
 * Names below match Slack @-mention labels; ids are not visible in the UI — use
 * `GET /api/slack/usergroups` and match `name` / `handle` to each squad.
 * Optional env override per key: `SQUAD_SLACK_<KEY>` wins over this map.
 *
 * SQZB: Slack may list Zebra Back / Web / Mobile separately; one Jira key — pick one group id
 * (or override with `SQUAD_SLACK_SQZB` to the subgroup you want notified).
 */
export const SQUAD_SLACK_MAP: Record<string, string> = {
  SQCC: "S0AH2LN20JE", // Squad Crocodile (@squad-crocodile)
  SQCY: "S07LX6D8NRK", // Squad Coyote (@squad-coyote)
  SQDO: "", // Squad DevOps — no matching Slack usergroup in workspace listing
  SQDP: "S05UELH00AH", // Squad Dolphin (@squad-dolphin)
  SQEG: "S0649BT7PV5", // Squad Eagle (@squad-eagle)
  SQGZ: "S06T1BMG2UA", // Squad Grizzly (@squad-grizzly)
  SQJG: "S03A6JJHZU2", // Squad Jaguar (@squad-jaguar)
  SQKA: "S08R71SBS3E", // Squad Koala (@squad-koala)
  SQKG: "S0AGZ83F5DK", // Squad Kangaroo (@squad-kangaroo)
  SQKM: "S0AGZ864HB7", // Squad Komodo (@squad-komodo)
  SQOT: "S08KQUH2BMG", // Squad Octopus (@squad-octopus)
  SQOW: "S064H8JRV2S", // Squad Owl (@squad-owl)
  SQPD: "S05R0LGDA48", // Squad Panda (@squad-panda); devs subteam: S08E910A666 @squad-panda-devs
  SQPM: "S03AWAT2588", // Squad Puma (@squad-puma)
  SQRC: "S07MNS0T6E4", // Squad Raccoon (@squad-raccoon)
  SQRN: "S07LL5F9AA2", // Squad Rhino (@squad-rhino)
  SQSH: "S05UW7FULUR", // Squad Shark (@squad-shark)
  SQSQ: "S08R6DEKKH9", // Squad Squid (@squad-squid)
  SQWH: "S0ASU0TAXA6", // Squad Whale (@squad-whale)
  SQXS: "", // Squad Cross — no matching Slack usergroup in listing
  SQZB: "S07MCJX5M33", // Squad Zebra (@squad-zebra); web/back/mobile: S0AAPP7SPS5 / S09F9GQKJJ1 / S0AAEDR28P6
};

export const OBSERVABILITY = {
  /** How often the auth-checker pings each integration. */
  AUTH_CHECK_INTERVAL_MS: 5 * 60 * 1000,
  /** Min gap between throttled "poll_skipped" info logs per poller. */
  SKIP_LOG_THROTTLE_MS: 60 * 1000,
  /** Consecutive poll failures before a poller escalates warn -> error. */
  FAILURE_ESCALATION_THRESHOLD: 3,
  /** How often the queue samples depth / busy / current-job-age gauges. */
  QUEUE_SAMPLE_INTERVAL_MS: 30 * 1000,
} as const;

export const RULES_LOADER = {
  SIZE_LIMIT: 32_000,
  PER_FILE_LIMIT: 8_000,
} as const;

export const TRIAGE_PROMPT = {
  TASK: [
    "1. Read the ticket details (description, parent ticket, attachments, comments)",
    "2. Navigate the relevant repository/repositories to understand the codebase area",
    "3. Determine if the bug is clearly described and actionable",
    "4. Respond with your assessment in the exact format below",
  ],
  PROCEED_CRITERIA: [
    "The bug is clearly described (what's broken, expected vs actual behavior)",
    "You can identify which repository/module is affected",
    "You can form a hypothesis about the root cause or at least know where to look",
    "There is enough context to start working on a fix",
  ],
  NEEDS_INFO_CRITERIA: [
    "The description is too vague to understand what's broken",
    "You cannot determine which repository or area of code is affected",
    "Critical reproduction steps are missing and you cannot infer them",
    "The ticket references external context (URLs, IDs, configs) that you cannot access",
    "Previous triage attempts (visible in comments) already explored all obvious paths",
  ],
  PROCEED_FORMAT: [
    "VERDICT: PROCEED",
    "SUMMARY: <what you analyzed, which repos/files you explored, your understanding of the bug>",
  ],
  NEEDS_INFO_FORMAT: [
    "VERDICT: NEEDS_INFO",
    "SUMMARY: <what you analyzed, which repos/files you explored, what you understood so far>",
    "QUESTIONS:",
    "- <specific question 1>",
    "- <specific question 2>",
  ],
  FOLLOWUP_INSTRUCTIONS: [
    "1. Read the error output carefully — it contains the exact file, line number, and error message",
    "2. Open the failing file(s) and go to the exact line(s) mentioned in the errors",
    "3. Fix each TypeScript/lint error while keeping the original bug fix intent intact",
    "4. Common fixes: add null checks, use optional chaining, narrow types with conditionals, add type assertions",
    "5. After fixing, verify there are no remaining lint or type errors in the modified files",
  ],
} as const;

/** Card/spec readiness (validate CLI): any issue type; same VERDICT block shape as TRIAGE_PROMPT for parsing. */
export const CARD_READINESS_PROMPT = {
  TASK: [
    "1. Read the full ticket (description, issue type, parent epic/story if any, acceptance criteria, attachments, comments).",
    "2. Optionally skim the listed repositories to judge whether scope and technical feasibility are clear enough.",
    "3. Judge whether the card is specific and complete enough that a developer or coding agent could implement it end-to-end without blocking ambiguity.",
    "4. Respond with your assessment in the exact format below.",
  ],
  PROCEED_CRITERIA: [
    "The goal and scope are clear (what to build or change, and what is out of scope / non-goals if relevant).",
    "Main user flows or behaviors are described; important edge cases, error states, or boundary conditions are called out or reasonably inferable.",
    "Acceptance criteria (or equivalent) exist and are testable, or the description is detailed enough to derive them.",
    "You can tell which repo(s) or subsystem(s) are likely involved, or the change is localized enough that discovery cost is low.",
    "Dependencies (APIs, flags, migrations, permissions, rollout) are identified or not required for a first version.",
  ],
  NEEDS_INFO_CRITERIA: [
    "The description is too vague to know what “done” looks like.",
    "Critical scenarios are missing (e.g. empty states, permissions, concurrency, idempotency, mobile vs web) and cannot be inferred safely.",
    "Acceptance criteria are missing or not testable, and the narrative does not compensate.",
    "Scope is unclear (too large, or overlaps multiple unrelated initiatives) without a suggested cut.",
    "External-only references (docs, designs, IDs) block implementation without access or a summary in the ticket.",
  ],
  PROCEED_FORMAT: [
    "VERDICT: PROCEED",
    "SUMMARY: <what you reviewed, gaps covered, confidence that a dev/AI can implement without further product clarification>",
  ],
  NEEDS_INFO_FORMAT: [
    "VERDICT: NEEDS_INFO",
    "SUMMARY: <what you understood so far, what is still unclear>",
    "QUESTIONS:",
    "- <specific question 1>",
    "- <specific question 2>",
  ],
} as const;
