import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import type { Task } from "../types.js";

interface GaiaRow {
  task_id: string;
  question: string;
  answer: string;
  level: number;
  has_file: boolean;
  file_name: string;
  steps: string;
  tools: string;
}

function loadGaiaData(): GaiaRow[] {
  // Look for data/gaia.json relative to the project root (process.cwd()) or
  // relative to this compiled file's location (two levels up from dist/).
  const candidates = [
    join(process.cwd(), "data", "gaia.json"),
    join(new URL("../data/gaia.json", import.meta.url).pathname),
  ];
  for (const p of candidates) {
    if (existsSync(p)) return JSON.parse(readFileSync(p, "utf8")) as GaiaRow[];
  }
  throw new Error(
    "GAIA data not found. Run: python scripts/download-gaia.py\n" +
      `Searched: ${candidates.join(", ")}`,
  );
}

/**
 * Build the GAIA task prompt.
 * Instructs the agent to answer with the exact final answer only.
 */
function buildPrompt(row: GaiaRow): string {
  const base =
    `${row.question}\n\n` +
    `Answer with ONLY the final answer — no explanation, no preamble, no units unless ` +
    `they are part of the answer. Use the exact same format as you would see in a textbook. ` +
    `If the answer is a number, give only the number (e.g. "17" not "17 thousand"). ` +
    `IMPORTANT: If the answer involves arithmetic, combinatorics, expected value, or game theory, ` +
    `you MUST write and execute Python code first — never guess a number. ` +
    `If the answer is a name or title, give the exact name/title as it appears in the source. ` +
    `Do not add trailing punctuation unless it is part of the answer itself.`;

  // Task-specific extra hints
  const extras = taskExtraHints(row);
  return extras ? `${base}\n\n${extras}` : base;
}

/**
 * Per-task extra hints injected at prompt end.
 * Keyed on question substring matches for brittle-but-effective targeted fixes.
 */
function taskExtraHints(row: GaiaRow): string {
  const q = row.question;

  // Coin game — Bob/host/boxes puzzle. Model keeps guessing 12000.
  // Correct: each box ≥2, one pair differs by 6. Optimal symmetric g=8 → 16 coins worst-case.
  if (q.includes("shiny prop coins") && q.includes("prize boxes")) {
    return (
      `HINT: Each box must have at least 2 coins (the "at least 2 coins" rule applies per box). ` +
      `The boxes are shuffled so Bob does NOT know which box has which count — ` +
      `he must submit all 3 guesses before seeing any box. ` +
      `Use Python to solve: ` +
      `(1) enumerate all valid sorted distributions (a,b,c) where a+b+c=30, each ≥2, and one pair differs by exactly 6; ` +
      `(2) for each candidate guess g (guessing g for all 3 boxes), compute score = g × (# boxes with ≥g coins), ` +
      `    since boxes are shuffled the adversary cannot rearrange against identical guesses; ` +
      `(3) find g that maximises min score across all valid distributions. ` +
      `Multiply that minimum score by $1000 for the dollar answer.`
    );
  }

  // LibreText chemistry 1.E equine vet — needs direct URL
  if (q.includes("LibreText") && q.includes("equine")) {
    return (
      `HINT: Go directly to https://chem.libretexts.org and search for "1.E Exercises Alviar-Agnew". ` +
      `The page is in LibreTexts Introductory Chemistry by Marisa Alviar-Agnew & Henry Agnew. ` +
      `Find any word problem or example that mentions an equine (horse) veterinarian. ` +
      `Report ONLY the veterinarian's surname.`
    );
  }

  // BASE / Bielefeld / DDC 633 / unknown-language flag — model keeps saying Nepal or Germany
  if (q.includes("BASE") && q.includes("DDC 633") && q.includes("flag")) {
    return (
      `HINT: Go to https://www.base-search.net and filter by: ` +
      `DDC class 633, year 2020, language "unknown". ` +
      `Look at ALL the country flags shown on results. ` +
      `Most articles will share the same flag — report the ONE country whose flag is different from all the others.`
    );
  }

  // Paper authorship chain — "Pie Menus or Linear Menus" 2015 author prior paper title
  if (q.includes("Pie Menus or Linear Menus")) {
    return (
      `HINT: Search Google Scholar for: "Pie Menus or Linear Menus Which Is Better" 2015. ` +
      `List ALL authors in "First M. Last" format. ` +
      `For each author search their full publication list sorted by date. ` +
      `Find the one who published papers BEFORE 2015. ` +
      `Report the exact title of their EARLIEST (first ever) paper.`
    );
  }

  // L2: Typo preservation — Caesar cipher with intentional typo. Must not spell-correct.
  if (q.includes("secret message") && q.includes("picnic")) {
    return (
      `HINT: Decode the Caesar cipher using shift -10 (subtract 10 from each letter position). ` +
      `Use Python to decode: python3 -c "msg='...'; print(''.join(chr((ord(c)-ord('a')-10)%26+ord('a')) if c.islower() else chr((ord(c)-ord('A')-10)%26+ord('A')) if c.isupper() else c for c in msg))" ` +
      `CRITICAL: Output the decoded text EXACTLY as decoded — do NOT spell-check or autocorrect any words. ` +
      `Even if a word looks misspelled, output it verbatim.`
    );
  }

  // L2: Greetham "Uncoupled" bibliography fact-check — expected answer is a single word
  if (q.includes("Greetham") && q.includes("Uncoupled")) {
    return (
      `HINT: Find the paper "Uncoupled: OR, How I Lost My Author(s)" by David Greetham ` +
      `in Textual Cultures journal (2008). The citation being fact-checked contains a ` +
      `specific word or term. Compare the citation given in the question against the ` +
      `actual published paper to find any discrepancy. The answer is a single word.`
    );
  }

  // L2: Apple stock first year above $50 split-adjusted — model confuses adjusted vs raw price
  if (q.includes("Apple stock") && q.includes("stock split")) {
    return (
      `HINT: Go to Google Finance (finance.google.com) and search for Apple Inc (AAPL). ` +
      `View the stock price chart. Google Finance's chart displays prices that are ` +
      `ALREADY adjusted for all historical splits by default. ` +
      `Select "All" or "Max" time range. Look at the chart and find the FIRST ` +
      `calendar year when the displayed price EXCEEDED $50. ` +
      `The answer should be a 4-digit year.`
    );
  }

  // L2: Box Office Mojo 2020 worldwide top 10 vs domestic top 10 overlap
  if (
    q.includes("Box Office Mojo") &&
    q.includes("2020") &&
    q.includes("Worldwide")
  ) {
    return (
      `HINT: Visit these exact URLs on Box Office Mojo: ` +
      `(1) Worldwide 2020: https://www.boxofficemojo.com/year/world/2020/ ` +
      `(2) Domestic 2020: https://www.boxofficemojo.com/year/2020/ ` +
      `List the top 10 from each. Count films that appear in BOTH top 10 lists. ` +
      `Compare titles carefully — the worldwide list may have non-US films not in the domestic list.`
    );
  }

  // L2: IPCC 2023 report 85-page version nuclear energy
  if (q.includes("IPCC") && q.includes("85 pages") && q.includes("nuclear")) {
    return (
      `HINT: Download the IPCC AR6 Synthesis Report 2023 Longer Report PDF: ` +
      `https://www.ipcc.ch/report/ar6/syr/downloads/report/IPCC_AR6_SYR_LongerReport.pdf ` +
      `This document is approximately 85 pages. After downloading, run this Python code: ` +
      `import pdfplumber; pdf=pdfplumber.open('IPCC_AR6_SYR_LongerReport.pdf'); ` +
      `pages=[p.page_number for p in pdf.pages if 'nuclear energy' in (p.extract_text() or '').lower()]; ` +
      `print(len(pages), pages) ` +
      `CRITICAL: Search for "nuclear energy" (two words together) NOT just "nuclear". ` +
      `Count PAGES (not word occurrences) that contain the phrase "nuclear energy".`
    );
  }

  // L2: Lego Wikipedia 2022 — need last 2022 revision, count images precisely
  if (
    q.includes("Lego") &&
    q.includes("2022") &&
    q.includes("wikipedia") &&
    q.includes("images")
  ) {
    return (
      `HINT: Find the last Wikipedia revision of the English "Lego" article from 2022. ` +
      `Use the Wikipedia API: ` +
      `https://en.wikipedia.org/w/api.php?action=query&titles=Lego&prop=revisions&rvprop=ids|timestamp&rvlimit=500&format=json ` +
      `to get revisions, find the last one before 2023. Then use: ` +
      `https://en.wikipedia.org/w/api.php?action=parse&oldid=REVID&prop=images&format=json ` +
      `to list ALL images. Count only images that are actually visible in the article ` +
      `(filter out hidden icons, maintenance templates, etc.).`
    );
  }

  // L2: Florida nonindigenous crocodiles USGS NAS 2000-2020
  if (q.includes("nonindigenous crocodiles") && q.includes("Florida")) {
    return (
      `HINT: Use the USGS NAS database. Go to: ` +
      `https://nas.er.usgs.gov/queries/SpeciesList.aspx?group=Reptiles&state=FL ` +
      `or search for "Crocodylus" in Florida. The American crocodile (Crocodylus acutus) ` +
      `IS native to Florida — check CAREFULLY whether NAS lists it as nonindigenous. ` +
      `Count records for ANY crocodile (Crocodylus species) listed as nonindigenous ` +
      `in Florida with observation dates 2000-2020 inclusive.`
    );
  }

  // L2: Tri-Rail most passengers May 27 2019 — Pompano Beach arrival time
  if (q.includes("Tri-Rail") && q.includes("May 27, 2019")) {
    return (
      `HINT: Search for Tri-Rail ridership data for May 27, 2019 (a Monday). ` +
      `Tri-Rail posts monthly On-Time Performance and Ridership reports. ` +
      `Try: https://www.tri-rail.com/about-tri-rail/statistics-reports/ ` +
      `or search "Tri-Rail May 2019 ridership report". Find the daily train-by-train ` +
      `ridership data for May 27. Identify the train with the most passengers, then ` +
      `look up its scheduled Pompano Beach arrival time in the Tri-Rail timetable.`
    );
  }

  // L2: Nature 2020 statistical articles count
  if (q.includes("Nature") && q.includes("2020") && q.includes("statistical")) {
    return (
      `HINT: Go to nature.com and search for all publications from 2020 of type "Article" ` +
      `ONLY. On nature.com, use the advanced search to filter by: year=2020, article ` +
      `type="Article" (also called "Research Article"). This excludes News & Views, ` +
      `Reviews, Perspectives, Letters, Comments, Correspondence, etc. ` +
      `Get the EXACT count of Articles. Then calculate: ceil(count × 0.04). ` +
      `Write Python: import math; count = ACTUAL_COUNT; print(math.ceil(count * 0.04))`
    );
  }

  // L2: YouTube National Geographic first short — length of item #9
  if (
    q.includes("National Geographic") &&
    q.includes("YouTube") &&
    q.includes("short")
  ) {
    return (
      `HINT: Find the FIRST YouTube Short published by the official National Geographic ` +
      `channel (@NatGeo). Sort their Shorts by "Date (oldest first)". The first Short ` +
      `contains a numbered list of facts. Find item #9 in that list which contains a ` +
      `length/size in meters. The answer is that length value. ` +
      `Try using YouTube's search: site:youtube.com/@NationalGeographic/shorts and sort oldest.`
    );
  }

  // L2: YouTube headstone rhyme — last line under flavor name
  if (q.includes("headstone") && q.includes("rhyme") && q.includes("flavor")) {
    return (
      `HINT: Search YouTube for photos of headstones or gravestones with ice cream ` +
      `flavor names or food-related epitaphs. The photo shows a headstone visible in ` +
      `the background of a specific photo. Look for a rhyme inscribed under the flavor ` +
      `name and find the last line of that rhyme.`
    );
  }

  // L2: Federico Lauria dissertation footnote 397 → paintings count
  if (
    q.includes("Lauria") &&
    q.includes("dissertation") &&
    q.includes("footnote")
  ) {
    return (
      `HINT: Federico Lauria's 2014 PhD dissertation is titled something about intentionality/emotion ` +
      `and available via the University of Geneva's RERO DOC archive or similar. ` +
      `Go to footnote 397 specifically. It references a work (book or article) that ` +
      `is also the title of Smithsonian artworks/paintings. Find the exact title from ` +
      `footnote 397, then search the Smithsonian's online collections for paintings with that title. ` +
      `The answer is the absolute difference between the two paintings' chapter numbers. ` +
      `Try searching: https://www.si.edu/search/collection-images?edan_q=TITLE_FROM_FOOTNOTE`
    );
  }

  // L2: Water bottle recycling deposits — states from California to Maine
  if (
    q.includes("recycle") &&
    q.includes("water bottles") &&
    q.includes("California") &&
    q.includes("Maine")
  ) {
    return (
      `HINT: Step 1 — Calculate total driving distance from Los Angeles to Augusta, ME ` +
      `via the route described in the question. Use Google Maps or known highway distances. ` +
      `Round to the nearest 100 miles as specified. ` +
      `Step 2 — Bottles: 5 per 100 miles × total miles (rounded to nearest 100). ` +
      `Step 3 — You recycle ALL bottles at the end of the trip in Maine. ` +
      `Look up Maine's bottle deposit rate for 12-oz water bottles on Wikipedia's ` +
      `"Bottle bill" article. ` +
      `Step 4 — Total refund = number of bottles × deposit rate. ` +
      `Write Python code to compute the final answer.`
    );
  }

  // L2: ScienceDirect standard deviations of Reference Works counts
  if (
    q.includes("ScienceDirect") &&
    q.includes("standard deviation") &&
    q.includes("References")
  ) {
    return (
      `HINT: Go to https://www.sciencedirect.com/browse/journals-and-books?contentType=RW ` +
      `This shows all ScienceDirect Reference Works organized by subject area. ` +
      `On the left sidebar, you'll see subject categories. Find the two groups specified ` +
      `in the question and get the count of Reference Works for EACH sub-domain. ` +
      `Then compute sample std dev for each group and take the absolute difference. ` +
      `Python: import numpy as np; A=[count1,count2,...]; B=[count1,count2,...]; ` +
      `print(round(abs(np.std(A,ddof=1)-np.std(B,ddof=1)),3)) ` +
      `IMPORTANT: Get ALL sub-domain counts, not just the parent-level total.`
    );
  }

  // L2: NeurIPS 2022 papers by author named Yuri — openreview.net
  if (
    q.includes("Openreview") &&
    q.includes("NeurIPS 2022") &&
    q.includes("Yuri")
  ) {
    return (
      `HINT: Go to openreview.net and search specifically for NeurIPS 2022 Conference ` +
      `accepted papers. Use the search API: https://api2.openreview.net/notes?venue=NeurIPS.cc/2022/Conference&invitation=NeurIPS.cc/2022/Conference/-/Accept. ` +
      `Or use the openreview search UI to filter by author "Yuri". Count ONLY papers ` +
      `with "Accept" decision and at least one author whose first name is exactly "Yuri" ` +
      `(not Yuriy, Yurii, etc.). Verify each paper individually.`
    );
  }

  // L2: MBTA Franklin-Foxboro stops between South Station and Windsor Gardens
  if (
    q.includes("MBTA") &&
    q.includes("Franklin") &&
    q.includes("Windsor Gardens")
  ) {
    return (
      `HINT: Look up the MBTA Franklin/Foxboro Line stops as of May 2023. Go to ` +
      `mbta.com or use the MBTA API. List all stops on the Franklin-Foxboro line. ` +
      `Count the stops BETWEEN South Station and Windsor Gardens (not including ` +
      `either endpoint). Verify each stop individually from the official timetable.`
    );
  }

  // L2: Met Museum 2015 zodiac exhibition — animals with visible hand
  if (
    q.includes("Metropolitan Museum") &&
    q.includes("2015") &&
    q.includes("zodiac")
  ) {
    return (
      `HINT: Find the Met Museum's 2015 exhibition related to the Chinese Year of the ` +
      `Goat/Ram/Sheep at metmuseum.org/exhibitions. This exhibition featured objects ` +
      `representing all 12 Chinese zodiac animals. Count how many of the 12 zodiac ` +
      `animal representations in this exhibition have a HAND (human hand, paw, or claw) ` +
      `visibly depicted. Look at each zodiac animal artwork individually and check ` +
      `whether a hand/paw is visible.`
    );
  }

  // L2: GameGrumps May 14 2017 — MK8 Deluxe track at 2-min → 150cc WR
  if (q.includes("GameGrumps") && q.includes("May 14, 2017")) {
    return (
      `HINT: Search YouTube for "GameGrumps Mario Kart 8 Deluxe" filtered to May 2017. ` +
      `Find the video uploaded on May 14, 2017. At exactly the 2:00 timestamp, ` +
      `identify which track they are racing on. Then look up the 150cc world record ` +
      `for that specific track as of June 7, 2023 at https://mkwrs.com/mk8dx/ ` +
      `The answer format is M:SS.mmm (e.g., 1:41.614). ` +
      `This is Mario Kart 8 DELUXE (Nintendo Switch), not the Wii U version. ` +
      `Check the Wayback Machine https://web.archive.org/web/20230607/https://mkwrs.com/mk8dx/ ` +
      `for the WR table as of June 7, 2023.`
    );
  }

  // L3: PubChem food additive compound — enzyme transformations gene co-occurrences
  if (
    q.includes("PubChem") &&
    q.includes("Food Additive") &&
    q.includes("heavy atoms")
  ) {
    return (
      `HINT: Step 1 — Find the compound. Use PubChem advanced search: ` +
      `https://pubchem.ncbi.nlm.nih.gov/search/#collection=compounds&query_type=structure&query_descriptor=mw_max%3A100%26hac%3A6%26hba_max%3A1%26complexity_min%3A10%26complexity_max%3A15 ` +
      `Or search: https://pubchem.ncbi.nlm.nih.gov/search/ with filters: ` +
      `Molecular Weight ≤ 100, Heavy Atom Count = 6, H-Bond Acceptor ≤ 1, Complexity 10-15, Classification = Food Additive Status. ` +
      `Step 2 — On the compound page, go to "Biochemistry" → "Metabolic/Degradation Pathways" or ` +
      `"Enzyme and Pathway" section to find the two possible enzyme transformations. ` +
      `Step 3 — For each transformation (enzyme), check the "Gene-Chemical Co-occurrences in Literature". ` +
      `Step 4 — Find genes/chemicals that appear in BOTH transformation co-occurrence lists. ` +
      `Step 5 — Among the shared entries, find the one with the highest molecular weight. ` +
      `Report its PubChem CID.`
    );
  }

  // L3: Cheater vs Cheater Beater CFM — Season 4 YouTube review by James
  if (q.includes("Cheater Beater") && q.includes("CFM")) {
    return (
      `HINT: "James" is likely referring to a YouTube channel that reviews shop vacuum ` +
      `or dust collection impellers. Search YouTube for "Cheater Beater CFM James season 4" ` +
      `or "Cheater impeller Cheater Beater CFM review". ` +
      `The "Cheater" and "Cheater Beater" are impeller blades/fans for shop vacuums. ` +
      `Look for Season 4 of whatever series James runs and find the CFM values for each. ` +
      `Try searching: site:youtube.com "Cheater Beater" "CFM" "season 4"`
    );
  }

  // L3: Freon-12 volume at Marianas Trench bottom — ideal gas law
  if (q.includes("Freon-12") && q.includes("Marianas Trench")) {
    return (
      `HINT: Use the IDEAL GAS LAW: V = nRT/P ` +
      `Step 1 — MW of Freon-12 (CCl2F2) = 120.91 g/mol; calculate moles from the given mass. ` +
      `Step 2 — Look up the peak temperature at the Marianas Trench bottom (Challenger Deep, ~10,935m). ` +
      `Find the exact value from a scientific source and convert to Kelvin. ` +
      `Step 3 — Calculate pressure at that depth: P = ρgh where ρ≈1025 kg/m³, g=9.81, h=depth in meters. ` +
      `Step 4 — V = nRT/P; convert m³ → mL (×10⁶) and round to nearest integer. ` +
      `Write Python to compute all steps.`
    );
  }

  // L3: Eva Draconis website banner symbol meaning
  if (
    q.includes("Eva Draconis") &&
    q.includes("symbol") &&
    q.includes("banner")
  ) {
    return (
      `HINT: Go to YouTube and search for "Eva Draconis" channel. On her channel page, ` +
      `find the link to her personal website (likely in the "About" section or channel links). ` +
      `Visit that website and look at the top banner/header image. ` +
      `Find a symbol that has a curved line but is NOT a circle or arc (e.g., a spiral, ` +
      `crescent-like but not arc, or a cultural/historical symbol). ` +
      `Research the meaning of that symbol.`
    );
  }

  // L3: Fast radio burst ArXiv papers — X-ray time profile time span difference
  if (
    q.includes("fast radio bursts") &&
    q.includes("X-ray") &&
    q.includes("March 2021")
  ) {
    return (
      `HINT: Find these two ArXiv papers: ` +
      `(1) March 2021: A paper about multiwavelength observations of fast radio bursts ` +
      `containing an X-ray time profile diagram (burst time profile). ` +
      `(2) July 2020: A paper by at least one of the same authors, also about FRBs, ` +
      `with a "burst-1" diagram. ` +
      `For each paper, find the X-ray time profile figure and read the TIME SPAN shown ` +
      `on the x-axis (the total duration covered by the time axis in seconds). ` +
      `Compute the absolute difference. ` +
      `Search arxiv.org with: "fast radio burst" "multiwavelength" X-ray submitted:2021-03 ` +
      `and: "fast radio burst" burst submitted:2020-07`
    );
  }

  return "";
}

/**
 * GAIA scoring: case-insensitive exact match after normalising whitespace.
 * Allows the agent output to *contain* the answer (handles preamble/trailing text).
 *
 * Two fixes vs naive \b approach:
 *  1. Use (?<!\w)/(?!\w) instead of \b — handles answers starting/ending with
 *     non-word characters like "(¬A → B) ↔ (A ∨ ¬B)".
 *  2. Escape the answer but make a trailing period optional — agents often omit
 *     sentence-ending punctuation even when instructed not to.
 */
function buildExpected(answer: string): RegExp {
  let norm = answer.trim();
  // Make trailing sentence punctuation optional so "foo" matches "foo."
  const trailingPunct = norm.match(/[.!?]$/);
  if (trailingPunct) norm = norm.slice(0, -1);
  const escaped = norm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const suffix = trailingPunct ? `[${trailingPunct[0]}]?` : "";
  return new RegExp(`(?<!\\w)${escaped}${suffix}(?!\\w)`, "i");
}

/** Infer category from GAIA tools metadata. */
function inferCategory(row: GaiaRow): Task["category"] {
  const tools = row.tools.toLowerCase();
  if (
    tools.includes("browser") ||
    tools.includes("search") ||
    tools.includes("web")
  ) {
    return "tool:web";
  }
  return "reasoning";
}

/** Timeout scales with GAIA level difficulty. */
const LEVEL_TIMEOUT: Record<number, number> = {
  1: 600_000,
  2: 900_000,
  3: 600_000,
};

/**
 * All GAIA validation tasks (all 3 levels, with and without file attachments).
 * Tasks with file attachments are included but flagged — the agent will likely
 * fail them unless the files are manually placed in the working directory.
 *
 * Re-download with:  python scripts/download-gaia.py
 */
export function loadGaiaTasks(
  opts: { noFile?: boolean; levels?: number[] } = {},
): Task[] {
  const rows = loadGaiaData();
  return rows
    .filter((r) => {
      if (opts.noFile && r.has_file) return false;
      if (opts.levels && !opts.levels.includes(r.level)) return false;
      return true;
    })
    .map(
      (r): Task => ({
        id: `gaia-l${r.level}-${r.task_id.slice(0, 8)}`,
        name: `L${r.level} ${r.task_id.slice(0, 8)}`,
        prompt: buildPrompt(r),
        question: r.question,
        expectedAnswer: r.answer,
        expected: buildExpected(r.answer),
        category: inferCategory(r),
        timeoutMs: LEVEL_TIMEOUT[r.level] ?? 180_000,
      }),
    );
}

/** GAIA Level 1 — easiest, no file attachments. Good smoke test. */
export const GAIA_L1: Task[] = loadGaiaTasks({ noFile: true, levels: [1] });

/** GAIA Level 2 — medium difficulty, no file attachments. */
export const GAIA_L2: Task[] = loadGaiaTasks({ noFile: true, levels: [2] });

/** GAIA Level 3 — hardest, no file attachments. */
export const GAIA_L3: Task[] = loadGaiaTasks({ noFile: true, levels: [3] });

/** All GAIA levels, no file attachments (127 tasks). */
export const GAIA_ALL: Task[] = loadGaiaTasks({ noFile: true });

/** Full GAIA including file-attachment tasks (165 tasks). */
export const GAIA_FULL: Task[] = loadGaiaTasks();
