/* jedai-data.jsx — tools catalog, scope-and-sequence, sight words, storage helpers.
   Pure data + helpers. No React. Exposed as window.JEDAI_DATA. */

(function () {
  const STORAGE_KEY = "jedai-tools-v1";

  /* =========================================================
     Tools catalog
     Six categories. Only "decodable" is wired end-to-end in v1.
     ========================================================= */

  const TOOLS = [
    {
      id: "decodable",
      label: "Decodable Passages",
      tagline: "Phonics-controlled stories with validator-enforced decodability.",
      colorVar: "--c-decodable",
      icon: "passage",
      status: "live"
    },
    {
      id: "reading",
      label: "Reading Passages",
      tagline: "Comprehension texts to a target grade/Lexile band.",
      colorVar: "--c-reading",
      icon: "scroll",
      status: "soon"
    },
    {
      id: "phonics",
      label: "Phonics Worksheets",
      tagline: "Word sorts, blending lines, dictation sets.",
      colorVar: "--c-phonics",
      icon: "grid",
      status: "soon"
    },
    {
      id: "worksheet",
      label: "Reading Worksheets",
      tagline: "Comprehension questions, vocabulary, response prompts.",
      colorVar: "--c-worksheet",
      icon: "check",
      status: "soon"
    },
    {
      id: "assess",
      label: "Assessments",
      tagline: "Phonemic awareness probes, fluency checks, decoding tests.",
      colorVar: "--c-assess",
      icon: "clipboard",
      status: "soon"
    },
    {
      id: "parents",
      label: "Parent Letters",
      tagline: "Weekly home-practice notes (bilingual EN/ZH supported).",
      colorVar: "--c-parents",
      icon: "letter",
      status: "soon"
    }
  ];

  /* =========================================================
     Scope-and-sequence (SoR-aligned, generic)
     Phases roughly aligned to UFLI / Wilson cadence. Edit freely
     to match your curriculum. Each grapheme listed in IPA-lite form
     for the validator to consume.

     graphemes:   spelling unit a learner has decoded
     phoneme:     the sound it represents (used only for display/eval)
     example:     teacher-facing word demoing the unit
     ========================================================= */

  // ============================================================
  // Scope-and-sequence — aligned to KG/G1/G2 curriculum v2.2.
  // Each grade is a separate array. Use getCumulativeScope(grade, week)
  // to get all taught content from KG W1 up to the (grade, week) target.
  // ============================================================

  const KG_SCOPE = [
    { week: 1, phase: "KG · Unit 0 — Letter ID", new: [] },
    { week: 2, phase: "KG · Unit 0 — Letter ID", new: [] },
    { week: 3, phase: "KG · Unit 0 — Letter ID", new: [] },
    { week: 4, phase: "KG · Unit 1 — First GPCs", new: [
      { g: "m", p: "/m/", ex: "mat" },
      { g: "s", p: "/s/", ex: "sat" }
    ]},
    { week: 5, phase: "KG · Unit 1 — First GPCs", new: [
      { g: "a", p: "/æ/", ex: "at" },
      { g: "t", p: "/t/", ex: "at" }
    ]},
    { week: 6, phase: "KG · Unit 1 — First GPCs", new: [
      { g: "p", p: "/p/", ex: "pat" },
      { g: "i", p: "/ɪ/", ex: "it" }
    ]},
    { week: 7, phase: "KG · Unit 1 — First blending", new: [
      { g: "n", p: "/n/", ex: "in" }
    ]},
    { week: 8, phase: "KG · Unit 1 — Review", new: [] },
    { week: 9, phase: "KG · Unit 2 — Expanding short vowels", new: [
      { g: "c", p: "/k/", ex: "cat" },
      { g: "o", p: "/ɒ/", ex: "on" }
    ]},
    { week: 10, phase: "KG · Unit 2 — Expanding short vowels", new: [
      { g: "d", p: "/d/", ex: "dad" },
      { g: "h", p: "/h/", ex: "hat" }
    ]},
    { week: 11, phase: "KG · Unit 2 — Review", new: [] },
    { week: 12, phase: "KG · Unit 2 — Expanding short vowels", new: [
      { g: "e", p: "/ɛ/", ex: "pet" },
      { g: "f", p: "/f/", ex: "fan" }
    ]},
    { week: 13, phase: "KG · Unit 2 — Expanding short vowels", new: [
      { g: "r", p: "/r/", ex: "run" }
    ]},
    { week: 14, phase: "KG · Unit 3 — Completing short vowels", new: [
      { g: "b", p: "/b/", ex: "bat" },
      { g: "l", p: "/l/", ex: "lap" }
    ]},
    { week: 15, phase: "KG · Unit 3 — Completing short vowels", new: [
      { g: "u", p: "/ʌ/", ex: "up" },
      { g: "g", p: "/g/", ex: "got" }
    ]},
    { week: 16, phase: "KG · Unit 3 — Completing short vowels", new: [
      { g: "k", p: "/k/", ex: "kit" },
      { g: "j", p: "/dʒ/", ex: "jam" }
    ]},
    { week: 17, phase: "KG · Unit 3 — Completing short vowels", new: [
      { g: "w", p: "/w/", ex: "win" }
    ]},
    { week: 18, phase: "KG · Unit 3 — Review", new: [] },
    { week: 19, phase: "KG · Unit 4 — Final consonants", new: [
      { g: "v", p: "/v/", ex: "van" },
      { g: "y", p: "/j/", ex: "yes" }
    ]},
    { week: 20, phase: "KG · Unit 4 — Final consonants", new: [
      { g: "z", p: "/z/", ex: "zip" },
      { g: "qu", p: "/kw/", ex: "quit" }
    ]},
    { week: 21, phase: "KG · Unit 4 — Final consonants", new: [
      { g: "x", p: "/ks/", ex: "fox" }
    ]},
    { week: 22, phase: "KG · Unit 4 — FLOSS rule", new: [
      { g: "ff", p: "/f/", ex: "off" },
      { g: "ll", p: "/l/", ex: "fell" },
      { g: "ss", p: "/s/", ex: "miss" },
      { g: "zz", p: "/z/", ex: "buzz" }
    ]},
    { week: 23, phase: "KG · Unit 4 — -ck rule", new: [
      { g: "ck", p: "/k/", ex: "back" }
    ]},
    { week: 24, phase: "KG · Unit 5 — Consonant digraphs", new: [
      { g: "sh", p: "/ʃ/", ex: "shop" }
    ]},
    { week: 25, phase: "KG · Unit 5 — Consonant digraphs", new: [
      { g: "ch", p: "/tʃ/", ex: "chin" },
      { g: "tch", p: "/tʃ/", ex: "match" }
    ]},
    { week: 26, phase: "KG · Unit 5 — Consonant digraphs", new: [
      { g: "th", p: "/θ,ð/", ex: "thin" }
    ]},
    { week: 27, phase: "KG · Unit 5 — Consonant digraphs", new: [
      { g: "wh", p: "/w/", ex: "when" }
    ]},
    { week: 28, phase: "KG · Unit 5 — Review", new: [] },
    { week: 29, phase: "KG · Unit 6 — s-blends", new: [
      { g: "sc-", p: "/sk/", ex: "scab" },
      { g: "sk-", p: "/sk/", ex: "skip" },
      { g: "sm-", p: "/sm/", ex: "smell" },
      { g: "sn-", p: "/sn/", ex: "snap" },
      { g: "sp-", p: "/sp/", ex: "spin" },
      { g: "st-", p: "/st/", ex: "stop" },
      { g: "sw-", p: "/sw/", ex: "swim" },
      { g: "sl-", p: "/sl/", ex: "slip" }
    ]},
    { week: 30, phase: "KG · Unit 6 — l-blends", new: [
      { g: "bl-", p: "/bl/", ex: "blot" },
      { g: "cl-", p: "/kl/", ex: "clip" },
      { g: "fl-", p: "/fl/", ex: "flag" },
      { g: "gl-", p: "/gl/", ex: "glad" },
      { g: "pl-", p: "/pl/", ex: "plan" }
    ]},
    { week: 31, phase: "KG · Unit 6 — r-blends", new: [
      { g: "br-", p: "/br/", ex: "brick" },
      { g: "cr-", p: "/kr/", ex: "crab" },
      { g: "dr-", p: "/dr/", ex: "drip" },
      { g: "fr-", p: "/fr/", ex: "frog" },
      { g: "gr-", p: "/gr/", ex: "grin" },
      { g: "pr-", p: "/pr/", ex: "prop" },
      { g: "tr-", p: "/tr/", ex: "trip" }
    ]},
    { week: 32, phase: "KG · Unit 6 — Final blends", new: [
      { g: "-nd", p: "/nd/", ex: "hand" },
      { g: "-nt", p: "/nt/", ex: "ant" },
      { g: "-nk", p: "/ŋk/", ex: "think" },
      { g: "-mp", p: "/mp/", ex: "lamp" },
      { g: "-st", p: "/st/", ex: "fast" },
      { g: "-ft", p: "/ft/", ex: "left" },
      { g: "-sk", p: "/sk/", ex: "desk" },
      { g: "-lt", p: "/lt/", ex: "belt" },
      { g: "-lp", p: "/lp/", ex: "help" },
      { g: "-ld", p: "/ld/", ex: "held" }
    ]},
    { week: 33, phase: "KG · Unit 6 — Inflectional plurals", new: [
      { g: "-s", p: "/s,z/", ex: "cats" },
      { g: "-es", p: "/ɪz/", ex: "wishes" }
    ]},
    { week: 34, phase: "KG · Unit 7 — CVCe preview (decode only)", new: [
      { g: "a_e", p: "/eɪ/", ex: "cake" },
      { g: "i_e", p: "/aɪ/", ex: "bike" },
      { g: "o_e", p: "/oʊ/", ex: "home" },
      { g: "u_e", p: "/juː/", ex: "cute" }
    ]},
    { week: 35, phase: "KG · Unit 7 — CVCe preview", new: [] },
    { week: 36, phase: "KG · Unit 7 — Consolidation", new: [] }
  ];

  const G1_SCOPE = [
    { week: 1, phase: "G1 · Unit 1 — KG review + /ŋ/", new: [
      { g: "ng", p: "/ŋ/", ex: "sing" },
      { g: "nk", p: "/ŋk/", ex: "think" }
    ]},
    { week: 2, phase: "G1 · Unit 1 — CVCe formal", new: [
      { g: "a_e", p: "/eɪ/", ex: "cake" }
    ]},
    { week: 3, phase: "G1 · Unit 1 — CVCe formal", new: [
      { g: "i_e", p: "/aɪ/", ex: "bike" }
    ]},
    { week: 4, phase: "G1 · Unit 1 — CVCe formal", new: [
      { g: "o_e", p: "/oʊ/", ex: "home" },
      { g: "u_e", p: "/juː/", ex: "cute" },
      { g: "e_e", p: "/iː/", ex: "Pete" }
    ]},
    { week: 5, phase: "G1 · Unit 1 — v-e rule", new: [] },
    { week: 6, phase: "G1 · Unit 2 — Long vowel teams (a)", new: [
      { g: "ai", p: "/eɪ/", ex: "rain" },
      { g: "ay", p: "/eɪ/", ex: "play" }
    ]},
    { week: 7, phase: "G1 · Unit 2 — Long vowel teams (e)", new: [
      { g: "ee", p: "/iː/", ex: "feet" },
      { g: "ea", p: "/iː/", ex: "leaf" }
    ]},
    { week: 8, phase: "G1 · Unit 2 — Long vowel teams (o)", new: [
      { g: "oa", p: "/oʊ/", ex: "boat" }
    ]},
    { week: 9, phase: "G1 · Unit 2 — Long vowel teams (i)", new: [
      { g: "igh", p: "/aɪ/", ex: "night" },
      { g: "ie", p: "/aɪ/", ex: "pie" }
    ]},
    { week: 10, phase: "G1 · Unit 2 — Long vowel teams (u)", new: [
      { g: "ue", p: "/uː/", ex: "blue" },
      { g: "ew", p: "/uː/", ex: "new" },
      { g: "oo", p: "/uː/", ex: "moon" }
    ]},
    { week: 11, phase: "G1 · Unit 2 — Vowel team review", new: [] },
    { week: 12, phase: "G1 · Unit 3 — R-controlled", new: [
      { g: "ar", p: "/ɑːr/", ex: "car" }
    ]},
    { week: 13, phase: "G1 · Unit 3 — R-controlled", new: [
      { g: "or", p: "/ɔːr/", ex: "for" }
    ]},
    { week: 14, phase: "G1 · Unit 3 — R-controlled", new: [
      { g: "er", p: "/ɝ/", ex: "her" },
      { g: "ir", p: "/ɝ/", ex: "bird" },
      { g: "ur", p: "/ɝ/", ex: "fur" }
    ]},
    { week: 15, phase: "G1 · Unit 3 — R-controlled", new: [
      { g: "are", p: "/ɛər/", ex: "care" },
      { g: "air", p: "/ɛər/", ex: "hair" }
    ]},
    { week: 16, phase: "G1 · Unit 3 — R-controlled", new: [
      { g: "ear", p: "/ɪər,ɝ,ɛər/", ex: "year" }
    ]},
    { week: 17, phase: "G1 · Unit 4 — Diphthongs", new: [
      { g: "oi", p: "/ɔɪ/", ex: "coin" },
      { g: "oy", p: "/ɔɪ/", ex: "boy" }
    ]},
    { week: 18, phase: "G1 · Unit 4 — Diphthongs", new: [
      { g: "ou", p: "/aʊ/", ex: "out" },
      { g: "ow", p: "/aʊ/", ex: "cow" }
    ]},
    { week: 19, phase: "G1 · Unit 4 — ow disambiguation", new: [] },
    { week: 20, phase: "G1 · Unit 4 — Diphthongs", new: [
      { g: "aw", p: "/ɔː/", ex: "saw" },
      { g: "au", p: "/ɔː/", ex: "haul" }
    ]},
    { week: 21, phase: "G1 · Unit 4 — Short oo", new: [] },
    { week: 22, phase: "G1 · Unit 5 — Soft c", new: [
      { g: "ce", p: "/s/", ex: "face" },
      { g: "ci", p: "/s/", ex: "city" },
      { g: "cy", p: "/s/", ex: "cycle" }
    ]},
    { week: 23, phase: "G1 · Unit 5 — Soft g", new: [
      { g: "ge", p: "/dʒ/", ex: "page" },
      { g: "gi", p: "/dʒ/", ex: "giant" },
      { g: "gy", p: "/dʒ/", ex: "gym" },
      { g: "dge", p: "/dʒ/", ex: "edge" }
    ]},
    { week: 24, phase: "G1 · Unit 5 — -ed allomorphs", new: [
      { g: "-ed", p: "/d,t,ɪd/", ex: "jumped" }
    ]},
    { week: 25, phase: "G1 · Unit 5 — Suffixes", new: [
      { g: "-ing", p: "/ɪŋ/", ex: "jumping" },
      { g: "-er", p: "/ɚ/", ex: "faster" },
      { g: "-est", p: "/ɪst/", ex: "fastest" }
    ]},
    { week: 26, phase: "G1 · Unit 5 — Doubling rule", new: [] },
    { week: 27, phase: "G1 · Unit 5 — Drop-e rule", new: [] },
    { week: 28, phase: "G1 · Unit 6 — Open syllables", new: [] },
    { week: 29, phase: "G1 · Unit 6 — Flex strategy", new: [] },
    { week: 30, phase: "G1 · Unit 6 — y as long e", new: [
      { g: "y_long_e", p: "/i/", ex: "happy" },
      { g: "y_long_i", p: "/aɪ/", ex: "fly" }
    ]},
    { week: 31, phase: "G1 · Unit 6 — VCe in 2-syl", new: [] },
    { week: 32, phase: "G1 · Unit 6 — Schwa", new: [] },
    { week: 33, phase: "G1 · Unit 7 — 3-letter blends", new: [
      { g: "scr-", p: "/skr/", ex: "scrap" },
      { g: "str-", p: "/str/", ex: "strap" },
      { g: "spr-", p: "/spr/", ex: "spring" },
      { g: "spl-", p: "/spl/", ex: "splash" },
      { g: "thr-", p: "/θr/", ex: "throw" },
      { g: "shr-", p: "/ʃr/", ex: "shrub" }
    ]},
    { week: 34, phase: "G1 · Unit 7 — Silent letters", new: [
      { g: "kn-", p: "/n/", ex: "knee" },
      { g: "wr-", p: "/r/", ex: "write" },
      { g: "gn-", p: "/n/", ex: "gnat" },
      { g: "-mb", p: "/m/", ex: "lamb" },
      { g: "-lk", p: "/k/", ex: "walk" }
    ]},
    { week: 35, phase: "G1 · Unit 7 — Prefixes", new: [
      { g: "un-", p: "/ʌn/", ex: "undo" },
      { g: "re-", p: "/ri/", ex: "redo" }
    ]},
    { week: 36, phase: "G1 · Unit 7 — Contractions + -ly", new: [
      { g: "-ly", p: "/li/", ex: "slowly" }
    ]},
    { week: 37, phase: "G1 · Unit 7 — EOY review", new: [] }
  ];

  const G2_SCOPE = [
    { week: 1, phase: "G2 · Unit 1 — G1 review", new: [] },
    { week: 2, phase: "G2 · Unit 1 — Consonant-le", new: [
      { g: "-ble", p: "/bəl/", ex: "table" }
    ]},
    { week: 3, phase: "G2 · Unit 1 — Consonant-le", new: [
      { g: "-dle", p: "/dəl/", ex: "candle" },
      { g: "-tle", p: "/təl/", ex: "little" }
    ]},
    { week: 4, phase: "G2 · Unit 1 — Consonant-le", new: [
      { g: "-kle", p: "/kəl/", ex: "pickle" },
      { g: "-ple", p: "/pəl/", ex: "apple" },
      { g: "-gle", p: "/gəl/", ex: "eagle" }
    ]},
    { week: 5, phase: "G2 · Unit 1 — Consonant-le", new: [
      { g: "-fle", p: "/fəl/", ex: "rifle" },
      { g: "-zle", p: "/zəl/", ex: "puzzle" }
    ]},
    { week: 6, phase: "G2 · Unit 2 — Variant vowel patterns", new: [
      { g: "ei", p: "/eɪ/", ex: "vein" }
    ]},
    { week: 7, phase: "G2 · Unit 2 — Variant vowel patterns", new: [
      { g: "eigh", p: "/eɪ/", ex: "eight" }
    ]},
    { week: 8, phase: "G2 · Unit 2 — Variant vowel patterns", new: [
      { g: "ey", p: "/eɪ/", ex: "they" }
    ]},
    { week: 9, phase: "G2 · Unit 2 — ie disambiguation", new: [] },
    { week: 10, phase: "G2 · Unit 2 — ough family", new: [
      { g: "ough", p: "varied", ex: "through" }
    ]},
    { week: 11, phase: "G2 · Unit 2 — ough family", new: [] },
    { week: 12, phase: "G2 · Unit 3 — Final stable syllable", new: [
      { g: "-tion", p: "/ʃən/", ex: "nation" }
    ]},
    { week: 13, phase: "G2 · Unit 3 — Final stable syllable", new: [
      { g: "-sion", p: "/ʒən,ʃən/", ex: "vision" }
    ]},
    { week: 14, phase: "G2 · Unit 3 — Final stable syllable", new: [
      { g: "-ture", p: "/tʃər/", ex: "picture" },
      { g: "augh", p: "/ɔː/", ex: "caught" }
    ]},
    { week: 15, phase: "G2 · Unit 3 — Final stable syllable", new: [
      { g: "-age", p: "/ɪdʒ/", ex: "village" }
    ]},
    { week: 16, phase: "G2 · Unit 3 — Final stable syllable", new: [
      { g: "-ance", p: "/əns/", ex: "dance" },
      { g: "-ence", p: "/əns/", ex: "science" }
    ]},
    { week: 17, phase: "G2 · Unit 3 — Review", new: [] },
    { week: 18, phase: "G2 · Unit 4 — Prefix review", new: [] },
    { week: 19, phase: "G2 · Unit 4 — Prefixes", new: [
      { g: "dis-", p: "/dɪs/", ex: "dislike" },
      { g: "mis-", p: "/mɪs/", ex: "mistake" }
    ]},
    { week: 20, phase: "G2 · Unit 4 — Prefixes", new: [
      { g: "pre-", p: "/pri/", ex: "preview" },
      { g: "post-", p: "/poʊst/", ex: "postpone" }
    ]},
    { week: 21, phase: "G2 · Unit 4 — Prefixes", new: [
      { g: "sub-", p: "/sʌb/", ex: "submarine" },
      { g: "super-", p: "/suːpər/", ex: "superman" }
    ]},
    { week: 22, phase: "G2 · Unit 4 — Prefixes", new: [
      { g: "inter-", p: "/ɪntər/", ex: "internet" },
      { g: "over-", p: "/oʊvər/", ex: "overdo" },
      { g: "under-", p: "/ʌndər/", ex: "underground" }
    ]},
    { week: 23, phase: "G2 · Unit 5 — Suffixes", new: [
      { g: "-ful", p: "/fəl/", ex: "hopeful" },
      { g: "-less", p: "/ləs/", ex: "hopeless" }
    ]},
    { week: 24, phase: "G2 · Unit 5 — -ly with y→i", new: [] },
    { week: 25, phase: "G2 · Unit 5 — Suffixes", new: [
      { g: "-ness", p: "/nəs/", ex: "kindness" },
      { g: "-ment", p: "/mənt/", ex: "movement" }
    ]},
    { week: 26, phase: "G2 · Unit 5 — Suffixes", new: [
      { g: "-able", p: "/əbəl/", ex: "readable" },
      { g: "-ible", p: "/əbəl/", ex: "possible" }
    ]},
    { week: 27, phase: "G2 · Unit 5 — Review", new: [] },
    { week: 28, phase: "G2 · Unit 6 — Greek roots", new: [
      { g: "tele-", p: "far", ex: "telescope" }
    ]},
    { week: 29, phase: "G2 · Unit 6 — Greek roots", new: [
      { g: "photo-", p: "light", ex: "photograph" }
    ]},
    { week: 30, phase: "G2 · Unit 6 — Greek roots", new: [
      { g: "bio-", p: "life", ex: "biology" },
      { g: "geo-", p: "earth", ex: "geography" }
    ]},
    { week: 31, phase: "G2 · Unit 6 — Greek roots", new: [
      { g: "graph-", p: "write/draw", ex: "paragraph" },
      { g: "phon-", p: "sound", ex: "phonics" }
    ]},
    { week: 32, phase: "G2 · Unit 6 — Latin roots", new: [
      { g: "port-", p: "carry", ex: "transport" },
      { g: "dict-", p: "say", ex: "predict" },
      { g: "scrib-", p: "write", ex: "describe" },
      { g: "script-", p: "write", ex: "scripture" },
      { g: "spect-", p: "look", ex: "inspect" }
    ]},
    { week: 33, phase: "G2 · Unit 6 — Roots review", new: [] },
    { week: 34, phase: "G2 · Unit 7 — Irregular plurals", new: [] },
    { week: 35, phase: "G2 · Unit 7 — y→ies plurals", new: [] },
    { week: 36, phase: "G2 · Unit 7 — Possessives", new: [] },
    { week: 37, phase: "G2 · Unit 7 — EOY review", new: [] }
  ];

  const SCOPE_BY_GRADE = { KG: KG_SCOPE, G1: G1_SCOPE, G2: G2_SCOPE };
  const GRADE_ORDER = ["KG", "G1", "G2"];

  /* Returns the flat sequence cumulative through (grade, week), including all
     prior grades in full. Items keep their original week numbers and gain a
     grade tag. */
  function getCumulativeScope(grade, throughWeek) {
    const result = [];
    for (const g of GRADE_ORDER) {
      const seq = SCOPE_BY_GRADE[g] || [];
      const limit = (g === grade) ? throughWeek : 999;
      for (const row of seq) {
        if (row.week > limit) break;
        result.push({ ...row, grade: g });
      }
      if (g === grade) break;
    }
    return result;
  }

  /* Legacy alias — cumulative through G2 W37. Used by validator code paths
     that don't yet pass a grade. */
  const SCOPE_AND_SEQUENCE = getCumulativeScope("G2", 37);

  /* Map UI grade level ("K","1","2","3") → curriculum scope grade ("KG","G1","G2"). */
  function scopeGradeForLevel(level) {
    return ({ "K": "KG", "1": "G1", "2": "G2", "3": "G2" })[level] || "G1";
  }

  /* Compute the cumulative set of taught grapheme strings (lowercase) up to
     (grade, week). If grade is omitted, defaults to G1. */
  function graphemesThroughWeek(week, grade) {
    const g = grade || "G1";
    const set = new Set();
    for (const row of getCumulativeScope(g, week)) {
      for (const u of row.new) set.add(u.g.toLowerCase());
    }
    return set;
  }

  /* New graphemes introduced at (grade, week). */
  function newGraphemesAtWeek(week, grade) {
    const g = grade || "G1";
    const seq = SCOPE_BY_GRADE[g] || [];
    const row = seq.find((r) => r.week === week);
    return row ? row.new.slice() : [];
  }

  /* Returns the unit/phase label for (grade, week). */
  function phaseAtWeek(week, grade) {
    const g = grade || "G1";
    const seq = SCOPE_BY_GRADE[g] || [];
    const row = seq.find((r) => r.week === week);
    return row?.phase || "";
  }

  /* Max week number for a grade. */
  function maxWeekForGrade(grade) {
    const seq = SCOPE_BY_GRADE[grade] || [];
    return seq.length ? seq[seq.length - 1].week : 0;
  }

  /* =========================================================
     Fry sight words (first 200 — high-frequency).
     Use for "allowed-but-not-decodable" exception list.
     ========================================================= */

  const FRY_SIGHT_WORDS_200 = [
    // 1-100
    "the","of","and","a","to","in","is","you","that","it",
    "he","was","for","on","are","as","with","his","they","i",
    "at","be","this","have","from","or","one","had","by","word",
    "but","not","what","all","were","we","when","your","can","said",
    "there","use","an","each","which","she","do","how","their","if",
    "will","up","other","about","out","many","then","them","these","so",
    "some","her","would","make","like","him","into","time","has","look",
    "two","more","write","go","see","number","no","way","could","people",
    "my","than","first","water","been","call","who","oil","its","now",
    "find","long","down","day","did","get","come","made","may","part",
    // 101-200
    "over","new","sound","take","only","little","work","know","place","year",
    "live","me","back","give","most","very","after","thing","our","just",
    "name","good","sentence","man","think","say","great","where","help","through",
    "much","before","line","right","too","mean","old","any","same","tell",
    "boy","follow","came","want","show","also","around","form","three","small",
    "set","put","end","does","another","well","large","must","big","even",
    "such","because","turn","here","why","ask","went","men","read","need",
    "land","different","home","us","move","try","kind","hand","picture","again",
    "change","off","play","spell","air","away","animal","house","point","page",
    "letter","mother","answer","found","study","still","learn","should","america","world"
  ];

  const FRY_SET = new Set(FRY_SIGHT_WORDS_200.map((w) => w.toLowerCase()));

  /* =========================================================
     Generation spec defaults
     ========================================================= */

  const PASSAGE_TEMPLATES = [
    { id: "narrative", label: "Narrative (story)" },
    { id: "informational", label: "Informational (facts)" },
    { id: "dialogue", label: "Dialogue (two characters)" },
    { id: "rhyme", label: "Rhyming verse" }
  ];

  const GRADE_LEVELS = [
    { id: "K", label: "Kindergarten" },
    { id: "1", label: "Grade 1" },
    { id: "2", label: "Grade 2" },
    { id: "3", label: "Grade 3" }
  ];

  // Curriculum-aligned defaults per scope grade. Sight-word caps are scaled
  // for the target word count — longer passages naturally pull in more
  // distinct high-frequency words.
  const SPEC_DEFAULTS_BY_GRADE = {
    KG: { wordCount: 80,  maxSentenceLen: 12, sightWordCap: 12, decodabilityFloor: 95 },
    G1: { wordCount: 150, maxSentenceLen: 15, sightWordCap: 25, decodabilityFloor: 90 },
    G2: { wordCount: 250, maxSentenceLen: 20, sightWordCap: 55, decodabilityFloor: 90 }
  };

  const DEFAULT_SPEC = {
    week: 14,
    grade: "1",
    template: "narrative",
    targetWords: "",       // newline or comma separated
    ...SPEC_DEFAULTS_BY_GRADE.G1,
    minTargetHits: 0,      // 0 = use all listed
    title: "",             // optional teacher-supplied title
    mode: "regular"        // "regular" = DeepSeek via OpenRouter, "pro" = Claude via Agent SDK
  };

  /* =========================================================
     Helpers
     ========================================================= */

  function makeId() {
    return Math.random().toString(36).slice(2) + Date.now().toString(36);
  }

  function tokenize(text) {
    // word-level tokens, lowercased, apostrophes preserved within (don't, it's)
    return String(text || "").toLowerCase().match(/[a-z]+(?:'[a-z]+)?/g) || [];
  }

  function splitSentences(text) {
    return String(text || "")
      .replace(/\s+/g, " ")
      .trim()
      .split(/(?<=[.!?]["'“”‘’]?)\s+(?=["'“”‘’]?[A-Z])/g)
      .filter(Boolean);
  }

  function sentenceLengths(text) {
    return splitSentences(text).map((s) => tokenize(s).length);
  }

  function normalizeWordList(raw) {
    if (!raw) return [];
    return String(raw)
      .toLowerCase()
      .split(/[\s,;]+/)
      .map((w) => w.trim().replace(/[^a-z']/g, ""))
      .filter(Boolean);
  }

  /* =========================================================
     Job model — one generation attempt for a tool.
     Stages: queued → planning → generating → validating → revising → editing → done | failed
     ========================================================= */

  function newJob(toolId, spec) {
    return {
      id: makeId(),
      toolId,
      spec: { ...DEFAULT_SPEC, ...spec },
      status: "queued",
      title: "",
      createdAt: new Date().toISOString(),
      draft: "",
      revisions: [],
      finalArtifact: "",
      validator: null,    // { passed, checks: [...] }
      trace: [],          // [{ stage, status, startedAt, finishedAt, info }]
      provenance: {},     // { stages: [{stage, model, license, sellable, ...}], sellable, summary }
      error: null
    };
  }

  function appendTrace(job, stage, status, info) {
    const now = Date.now();
    const last = job.trace[job.trace.length - 1];
    if (last && last.stage === stage && last.status === "active" && status !== "active") {
      last.status = status;
      last.finishedAt = now;
      if (info) last.info = info;
      return job;
    }
    job.trace.push({
      stage, status,
      startedAt: now,
      finishedAt: status === "active" ? null : now,
      info: info || ""
    });
    return job;
  }

  /* =========================================================
     Local storage — caches recent jobs per teacher.
     ========================================================= */

  function loadState() {
    try {
      const saved = JSON.parse(localStorage.getItem(STORAGE_KEY));
      if (saved && typeof saved === "object") return normalizeState(saved);
    } catch (e) {}
    return { jobs: [], currentToolId: null };
  }

  function normalizeState(s) {
    return {
      jobs: Array.isArray(s.jobs) ? s.jobs.slice(0, 100) : [],
      currentToolId: typeof s.currentToolId === "string" ? s.currentToolId : null
    };
  }

  function saveState(state) {
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch (e) {}
  }

  /* =========================================================
     Export
     ========================================================= */

  window.JEDAI_DATA = {
    TOOLS,
    SCOPE_AND_SEQUENCE,
    SCOPE_BY_GRADE,
    KG_SCOPE,
    G1_SCOPE,
    G2_SCOPE,
    GRADE_ORDER,
    getCumulativeScope,
    scopeGradeForLevel,
    maxWeekForGrade,
    phaseAtWeek,
    FRY_SIGHT_WORDS_200,
    FRY_SET,
    PASSAGE_TEMPLATES,
    GRADE_LEVELS,
    DEFAULT_SPEC,
    SPEC_DEFAULTS_BY_GRADE,
    graphemesThroughWeek,
    newGraphemesAtWeek,
    tokenize,
    splitSentences,
    sentenceLengths,
    normalizeWordList,
    newJob,
    appendTrace,
    loadState,
    saveState,
    makeId
  };
})();
