#!/usr/bin/env python3
"""
Generate comprehensive JavaScript training data for Dev-AI.
Covers: security, performance, modern patterns, DOM, async, Node.js, testing, build tools.
Target: 120-150 high-quality entries.
"""
import json
import os
import random
from pathlib import Path

BASE = Path(__file__).parent.parent / "data" / "processed"

def g(issue, bad, good):
    return {
        "messages": [
            {"role": "user", "content": f"Review this JavaScript code:\n\n```javascript\n{bad}\n```"},
            {"role": "assistant", "content": f"**Issue: {issue}**\n\n**Fix:**\n```javascript\n{good}\n```"}
        ],
        "source": "javascript_training",
        "tags": ["javascript", issue.lower().replace(" ", "_")]
    }


entries = []

# =============================================================================
# SECURITY (15 entries)
# =============================================================================

entries.append(g(
    "XSS via innerHTML",
    "document.getElementById('output').innerHTML = userInput;",
    "const el = document.getElementById('output');\nel.textContent = userInput;\n// textContent escapes HTML. innerHTML renders it."
))

entries.append(g(
    "XSS via eval()",
    "const result = eval(userExpression);",
    "// Never eval user input.\n// Use JSON.parse for JSON data:\nconst data = JSON.parse(userInput);\n// Or a safe expression parser library."
))

entries.append(g(
    "XSS via document.write()",
    "document.write('<div>' + userComment + '</div>');",
    "const container = document.getElementById('comments');\nconst div = document.createElement('div');\ndiv.textContent = userComment;\ncontainer.appendChild(div);"
))

entries.append(g(
    "Insecure Cookie",
    "document.cookie = 'session=' + token;",
    "document.cookie = 'session=' + token + '; SameSite=Strict; Secure; Path=/';\n// Secure flag = HTTPS only\n// SameSite prevents CSRF\n// Path limits scope"
))

entries.append(g(
    "Prototype Pollution",
    "function merge(target, source) {\n  for (let key in source) {\n    target[key] = source[key];\n  }\n}",
    "function merge(target, source) {\n  for (let key in source) {\n    if (Object.prototype.hasOwnProperty.call(source, key)) {\n      if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n      target[key] = source[key];\n    }\n  }\n}"
))

entries.append(g(
    "CSP Missing",
    "<!DOCTYPE html>\n<html>\n<head>\n  <script src=\"app.js\"></script>\n</head>",
    "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-Security-Policy\" \n    content=\"default-src 'self'; script-src 'self'; object-src 'none'\">\n  <script src=\"app.js\"></script>\n</head>"
))

entries.append(g(
    "Client-side Secret",
    "const API_KEY = 'sk_live_abc123';\nfetch('/api/data', { headers: { 'Authorization': API_KEY } });",
    "// Never store secrets in client-side code.\n// Use a backend proxy:\nfetch('/api/proxy/data')\n  .then(r => r.json());\n// Server adds the API key server-side."
))

entries.append(g(
    "Open Redirect",
    "const url = req.query.redirect;\nres.redirect(url);",
    "const allowedHosts = ['example.com', 'app.example.com'];\nconst url = req.query.redirect;\nconst parsed = new URL(url, 'https://example.com');\nif (!allowedHosts.includes(parsed.hostname)) {\n  return res.redirect('/');\n}\nres.redirect(url);"
))

entries.append(g(
    "No CSRF Protection",
    "app.post('/transfer', (req, res) => {\n  // Process transfer\n});",
    "const csrf = require('csurf');\nconst csrfProtection = csrf({ cookie: true });\n\napp.get('/form', csrfProtection, (req, res) => {\n  res.json({ csrfToken: req.csrfToken() });\n});\n\napp.post('/transfer', csrfProtection, (req, res) => {\n  // Token validated automatically\n});"
))

entries.append(g(
    "Unsafe Redirect",
    "window.location = userInput;",
    "// Validate before redirecting:\nconst allowedUrls = ['/dashboard', '/profile', '/settings'];\nif (allowedUrls.includes(userInput)) {\n  window.location.href = userInput;\n}"
))

entries.append(g(
    "Sensitive Data in URL",
    "fetch(`/api/users/${userId}?token=${authToken}`)",
    "fetch(`/api/users/${userId}`, {\n  headers: { 'Authorization': `Bearer ${authToken}` }\n})"
))

entries.append(g(
    "Insecure deserialization",
    "const data = eval('(' + jsonString + ')');",
    "const data = JSON.parse(jsonString);\n// JSON.parse is safe. eval() executes arbitrary code."
))

entries.append(g(
    "Missing Rate Limiting",
    "app.post('/login', async (req, res) => {\n  // Auth check - no rate limit\n});",
    "const rateLimit = require('express-rate-limit');\nconst loginLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: 'Too many login attempts'\n});\n\napp.post('/login', loginLimiter, async (req, res) => {\n  // Auth check\n});"
))

entries.append(g(
    "Unsafe iframe",
    "<iframe src=\"userProvidedUrl\"></iframe>",
    "<iframe src=\"userProvidUrl\" sandbox=\"allow-scripts allow-sameorigin\"></iframe>\n// sandbox restricts frame capabilities\n// Only allow specific origins in production"
))

entries.append(g(
    "No input sanitization",
    "app.get('/search', (req, res) => {\n  const query = req.query.q;\n  res.render('results', { query });\n});",
    "const DOMPurify = require('dompurify');\nconst { JSDOM } = require('jsdom');\nconst window = new JSDOM('').window;\nconst purify = DOMPurify(window);\n\napp.get('/search', (req, res) => {\n  const query = purify.sanitize(req.query.q);\n  res.render('results', { query });\n});"
))

# =============================================================================
# PERFORMANCE (15 entries)
# =============================================================================

entries.append(g(
    "var instead of let/const",
    "var count = 0;\nvar name = 'test';\nvar MAX = 100;",
    "let count = 0;\nconst name = 'test';\nconst MAX = 100;\n// const for values that don't change\n// let for reassignable variables\n// var is function-scoped and hoisted"
))

entries.append(g(
    "String concatenation in loop",
    "let result = '';\nfor (let i = 0; i < items.length; i++) {\n  result += items[i];\n}",
    "const result = items.join('');\n// join() is optimized and avoids string re-allocation"
))

entries.append(g(
    "Inefficient DOM manipulation",
    "for (const item of items) {\n  document.getElementById('list').innerHTML += `<li>${item}</li>`;\n}",
    "// Build once, insert once:\nconst fragment = document.createDocumentFragment();\nitems.forEach(item => {\n  const li = document.createElement('li');\n  li.textContent = item;\n  fragment.appendChild(li);\n});\ndocument.getElementById('list').appendChild(fragment);"
))

entries.append(g(
    "Using array.indexOf for large sets",
    "if (allowedList.indexOf(value) !== -1) { ... }\n// allowedList has thousands of items",
    "const allowedSet = new Set(allowedList);\nif (allowedSet.has(value)) { ... }\n// Set lookups are O(1) vs O(n)"
))

entries.append(g(
    "Blocking the main thread",
    "function processData(hugeArray) {\n  // Heavy computation blocks UI\n  return hugeArray.map(x => expensiveCalc(x));\n}",
    "// Offload to Web Worker:\nconst worker = new Worker('calc-worker.js');\nworker.postMessage(hugeArray);\nworker.onmessage = (e) => {\n  const results = e.data;\n  // UI stays responsive\n};"
))

entries.append(g(
    "Unnecessary array copy",
    "const copy = [...array];\nconst filtered = copy.filter(x => x > 0);\nconst mapped = filtered.map(x => x * 2);",
    "// Chain operations without intermediate copies:\nconst result = array.filter(x => x > 0).map(x => x * 2);\n// Each step returns a new array, but no unnecessary spread"
))

entries.append(g(
    "For loop instead of built-in",
    "let sum = 0;\nfor (let i = 0; i < arr.length; i++) {\n  sum += arr[i];\n}",
    "const sum = arr.reduce((acc, val) => acc + val, 0);\n// Or for simple sum:\nconst sum = arr.reduce((a, b) => a + b, 0);"
))

entries.append(g(
    "Memory leak with setInterval",
    "setInterval(() => {\n  fetch('/api/data').then(updateUI);\n}, 5000);\n// Never cleared - keeps running after component unmount",
    "const intervalId = setInterval(() => {\n  fetch('/api/data').then(updateUI);\n}, 5000);\n// Clean up:\nclearInterval(intervalId);"
))

entries.append(g(
    "Event listener leak",
    "function setup() {\n  document.addEventListener('click', handleClick);\n}\n// Called multiple times, adding duplicate listeners",
    "// Use a flag or remove before adding:\ndocument.removeEventListener('click', handleClick);\ndocument.addEventListener('click', handleClick);\n// Better: use event delegation\nparentElement.addEventListener('click', (e) => {\n  if (e.target.matches('.button')) handleClick(e);\n});"
))

entries.append(g(
    "Large bundle - full lodash import",
    "import _ from 'lodash';\n_.debounce(...);\n// Imports entire lodash (~70KB)",
    "import debounce from 'lodash/debounce';\n// Tree-shakeable - only imports what's used\n// Or use a lightweight alternative:"
))

entries.append(g(
    "Unoptimized image loading",
    "<img src=\"photo.jpg\"> <!-- 4MB image -->",
    "<img src=\"photo.webp\" \n     srcset=\"photo-400w.webp 400w, photo-800w.webp 800w\"\n     sizes=\"(max-width: 600px) 400px, 800px\"\n     loading=\"lazy\"\n     width=\"800\" height=\"600\">\n// WebP format, responsive, lazy-loaded, sized"
))

entries.append(g(
    "Synchronous XHR",
    "const xhr = new XMLHttpRequest();\nxhr.open('GET', '/data', false); // synchronous!\nxhr.send();\nconst data = xhr.responseText;",
    "fetch('/data')\n  .then(r => r.json())\n  .then(data => {\n    // Process response\n  });\n// Or async/await:\nconst resp = await fetch('/data');\nconst data = await resp.json();"
))

entries.append(g(
    "Using setTimeout(0) for async",
    "function asyncWork() {\n  setTimeout(() => {\n    // 'async' work\n  }, 0);\n}",
    "// Use Promise for proper async semantics:\nasync function asyncWork() {\n  await new Promise(resolve => setTimeout(resolve, 0));\n  // 'async' work\n}\n// Or queueMicrotask for same-tick scheduling"
))

entries.append(g(
    "Inefficient reflow",
    "const el = document.getElementById('box');\nel.style.width = '100px';  // forces reflow\nconst h = el.offsetHeight;  // forces reflow\nel.style.height = h + 'px'; // forces reflow",
    "// Batch reads and writes:\nconst h = el.offsetHeight;  // read first\nrequestAnimationFrame(() => {\n  el.style.width = '100px';   // write batched\n  el.style.height = h + 'px'; // write batched\n});"
))

entries.append(g(
    "Not using requestAnimationFrame",
    "function animate() {\n  updatePosition();\n  setTimeout(animate, 16);\n}",
    "function animate() {\n  updatePosition();\n  requestAnimationFrame(animate);\n  // Syncs with display refresh rate, smoother animation\n}"
))

# =============================================================================
# MODERN PATTERNS & ES6+ (15 entries)
# =============================================================================

entries.append(g(
    "function declaration in block scope",
    "if (condition) {\n  function helper() { return 1; }\n  return helper();\n} // helper leaks in non-strict mode",
    "if (condition) {\n  const helper = () => 1;\n  return helper();\n  // Arrow functions are properly block-scoped\n}"
))

entries.append(g(
    "this binding lost",
    "class Timer {\n  constructor() {\n    this.seconds = 0;\n    setInterval(function() {\n      this.seconds++; // 'this' is undefined\n    }, 1000);\n  }\n}",
    "class Timer {\n  constructor() {\n    this.seconds = 0;\n    setInterval(() => {\n      this.seconds++; // Arrow function preserves 'this'\n    }, 1000);\n  }\n}"
))

entries.append(g(
    "Using Object.keys for iteration",
    "for (const key of Object.keys(obj)) {\n  console.log(key, obj[key]);\n}",
    "for (const [key, value] of Object.entries(obj)) {\n  console.log(key, value);\n}"
))

entries.append(g(
    "Manual deep clone",
    "function deepClone(obj) {\n  const clone = {};\n  for (let key in obj) {\n    clone[key] = typeof obj[key] === 'object'\n      ? deepClone(obj[key]) : obj[key];\n  }\n  return clone;\n}",
    "// Built-in for most cases:\nconst clone = structuredClone(original);\n// Handles circular refs, Dates, Maps, Sets\n// Or: JSON.parse(JSON.stringify(obj)) for simple data"
))

entries.append(g(
    "Using == instead of ==",
    "if (value == 0) { ... }\nif (value == undefined) { ... }",
    "if (value === 0) { ... }\nif (value === undefined) { ... }\n// === checks both value and type\n// == performs type coercion (source of bugs)"
))

entries.append(g(
    "Missing optional chaining",
    "const city = user && user.address && user.address.city;",
    "const city = user?.address?.city;\n// Optional chaining (?.) - cleaner null safety"
))

entries.append(g(
    "Missing nullish coalescing",
    "const port = options.port || 8080;\n// Problem: port=0 evaluates to false",
    "const port = options.port ?? 8080;\n// ?? only falls back on null/undefined, not 0 or ''"
))

entries.append(g(
    "Using arguments object",
    "function sum() {\n  let total = 0;\n  for (let i = 0; i < arguments.length; i++) {\n    total += arguments[i];\n  }\n  return total;\n}",
    "function sum(...args) {\n  return args.reduce((acc, val) => acc + val, 0);\n  // Rest parameters are proper arrays\n  // No 'this' binding issues\n}"
))

entries.append(g(
    "Template literal concatenation",
    "const greeting = 'Hello, ' + name + '! You are ' + age + ' years old.';",
    "const greeting = `Hello, ${name}! You are ${age} years old.`;\n// Template literals are cleaner and support expressions"
))

entries.append(g(
    "Missing destructuring",
    "const name = user.name;\nconst email = user.email;\nconst role = user.role;",
    "const { name, email, role } = user;\n// Or with renaming:\nconst { name: userName, email } = user;"
))

entries.append(g(
    "Array map for side effects",
    "items.map(item => {\n  console.log(item);\n  process(item);\n});",
    "items.forEach(item => {\n  console.log(item);\n  process(item);\n});\n// map() returns a new array - use forEach() for side effects"
))

entries.append(g(
    "Missing Symbol usage",
    "class Cache {\n  get(key) { return this.data[key]; }\n  set(key, val) { this.data[key] = val; }\n}\n// Collision risk with user data keys",
    "const _data = Symbol('data');\nclass Cache {\n  constructor() { this[_data] = new Map(); }\n  get(key) { return this[_data].get(key); }\n  set(key, val) { this[_data].set(key, val); }\n  // Symbol keys won't collide with user data\n}"
))

entries.append(g(
    "Using for...in on arrays",
    "for (let i in array) {\n  process(array[i]); // wrong iteration order, includes properties\n}",
    "for (let i = 0; i < array.length; i++) {\n  process(array[i]);\n}\n// Or:\nfor (const item of array) {\n  process(item);\n}"
))

entries.append(g(
    "Missing class field syntax",
    "class Button {\n  constructor() {\n    this.handleClick = this.handleClick.bind(this);\n  }\n  handleClick() { ... }\n}",
    "class Button {\n  handleClick = () => { ... };\n  // Class fields are auto-bound to the instance\n}"
))

entries.append(g(
    "Object spread for partial update",
    "const updated = {};\nfor (const key in original) {\n  updated[key] = original[key];\n}\nupdated.name = newName;",
    "const updated = { ...original, name: newName };\n// Spread operator copies and overrides in one line"
))

# =============================================================================
# ASYNC / PROMISES (12 entries)
# =============================================================================

entries.append(g(
    "Callback hell",
    "getData(function(a) {\n  getMore(a, function(b) {\n    getMore(b, function(c) {\n      getMore(c, function(d) {\n        // ...\n      });\n    });\n  });\n});",
    "async function load() {\n  const a = await getData();\n  const b = await getMore(a);\n  const c = await getMore(b);\n  const d = await getMore(c);\n  // Linear and readable\n}"
))

entries.append(g(
    "Uncaught promise rejection",
    "fetch('/api/data')\n  .then(r => r.json())\n  .then(data => updateUI(data));\n// No .catch() - silent failure",
    "async function loadData() {\n  try {\n    const resp = await fetch('/api/data');\n    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\n    const data = await resp.json();\n    updateUI(data);\n  } catch (err) {\n    console.error('Load failed:', err);\n    showErrorMessage(err.message);\n  }\n}"
))

entries.append(g(
    "Promise.all without error handling",
    "const [users, posts] = await Promise.all([\n  fetchUsers(),\n  fetchPosts()\n]);\n// One failure cancels everything",
    "const [users, posts] = await Promise.allSettled([\n  fetchUsers(),\n  fetchPosts()\n]);\n// All resolve individually\nconst userList = users.status === 'fulfilled' ? users.value : [];\nconst postList = posts.status === 'fulfilled' ? posts.value : [];"
))

entries.append(g(
    "Race condition with shared state",
    "let count = 0;\nasync function increment() {\n  await delay(100);\n  count++; // Non-atomic with async gap\n}",
    "// Use a lock or atomic operations:\nlet count = 0;\nconst lock = { held: false };\nasync function increment() {\n  while (lock.held) await delay(10);\n  lock.held = true;\n  try {\n    await delay(100);\n    count++;\n  } finally {\n    lock.held = false;\n  }\n}"
))

entries.append(g(
    "Missing Promise.all for parallel requests",
    "const users = await fetchUsers();\nconst posts = await fetchPosts();\nconst comments = await fetchComments();\n// Sequential - waits for each",
    "const [users, posts, comments] = await Promise.all([\n  fetchUsers(),\n  fetchPosts(),\n  fetchComments()\n]);\n// Parallel - total time = slowest request"
))

entries.append(g(
    "Stale closure in async callback",
    "for (let i = 0; i < items.length; i++) {\n  fetch(items[i].url).then(data => {\n    items[i] = data; // 'i' may have changed\n  });\n}",
    "const results = await Promise.all(\n  items.map(async (item, index) => {\n    const data = await fetch(item.url);\n    return data;\n  })\n);\nitems = results;"
))

entries.append(g(
    "Missing timeout for fetch",
    "const resp = await fetch('/api/data');",
    "const controller = new AbortController();\nconst timeout = setTimeout(() => controller.abort(), 5000);\ntry {\n  const resp = await fetch('/api/data', {\n    signal: controller.signal\n  });\n} finally {\n  clearTimeout(timeout);\n}"
))

entries.append(g(
    "Fire and forget promise",
    "function handle() {\n  logEvent('click'); // Promise returned but not awaited\n  return navigate('/next');\n}",
    "// If you don't need the result, still handle errors:\nlogEvent('click').catch(err => console.error('Log failed:', err));\nreturn navigate('/next');"
))

entries.append(g(
    "Await in loop instead of parallel",
    "for (const id of ids) {\n  const item = await fetchItem(id);\n  results.push(item);\n}\n// Sequential - slow for many items",
    "// Parallel:\nconst results = await Promise.all(\n  ids.map(id => fetchItem(id))\n);\n// With concurrency limit:\nconst results = await pLimit(ids.map(id => () => fetchItem(id)));"
))

entries.append(g(
    "Not using async iterators",
    "async function getAllPages(url) {\n  let page = 1;\n  const all = [];\n  while (true) {\n    const resp = await fetch(`${url}?page=${page}`);\n    const data = await resp.json();\n    if (data.items.length === 0) break;\n    all.push(...data.items);\n    page++;\n  }\n  return all;\n}",
    "async function* pageIterator(url) {\n  let page = 1;\n  while (true) {\n    const resp = await fetch(`${url}?page=${page}`);\n    const data = await resp.json();\n    if (data.items.length === 0) break;\n    yield* data.items;\n    page++;\n  }\n}\n// Consumer:\nconst all = [];\nfor await (const item of pageIterator(url)) {\n  all.push(item);\n}"
))

entries.append(g(
    "Double await",
    "const data = await Promise.resolve(value);\n// Unnecessary wrapping",
    "const data = value;\n// If already resolved, no need for await\n// Or if it might be a promise:\nconst data = await Promise.resolve(value);\n// Only wrap if the value is uncertain"
))

entries.append(g(
    "Missing finally cleanup",
    "async function load() {\n  showSpinner();\n  const data = await fetch('/api/data');\n  hideSpinner();\n  return data;\n  // Spinner stays if fetch throws\n}",
    "async function load() {\n  showSpinner();\n  try {\n    const data = await fetch('/api/data');\n    return data;\n  } finally {\n    hideSpinner(); // Always runs\n  }\n}"
))

# =============================================================================
# DOM & BROWSER (12 entries)
# =============================================================================

entries.append(g(
    "QuerySelector in loop",
    "for (const id of ids) {\n  const el = document.getElementById(id);\n  el.classList.add('active');\n}",
    "// Cache references:\nconst els = ids.map(id => document.getElementById(id));\nfor (const el of els) {\n  el.classList.add('active');\n}\n// Or: document.querySelectorAll('#id1, #id2, ...')"
))

entries.append(g(
    "Using getElementsBy* collections",
    "const items = document.getElementsByClassName('item');\nfor (let i = 0; i < items.length; i++) { ... }\n// Live collection - re-evaluates on DOM changes",
    "const items = document.querySelectorAll('.item');\nitems.forEach(item => { ... });\n// querySelectorAll returns static NodeList"
))

entries.append(g(
    "Missing event delegation",
    "document.querySelectorAll('.btn').forEach(btn => {\n  btn.addEventListener('click', handleClick);\n});\n// Breaks when elements are dynamically added",
    "document.getElementById('container').addEventListener('click', (e) => {\n  const btn = e.target.closest('.btn');\n  if (btn) handleClick(e);\n});\n// Works for dynamic elements too"
))

entries.append(g(
    "Inline event handlers",
    "<button onclick=\"handleClick()\">Click</button>",
    "<button data-action=\"click\">Click</button>\n\n// In JavaScript:\ndocument.querySelector('[data-action=\"click\"]').addEventListener('click', handleClick);"
))

entries.append(g(
    "Memory leak - detached DOM reference",
    "const el = document.getElementById('chart');\nconst chart = new Chart(el);\n// el is removed from DOM but chart still references it",
    "const el = document.getElementById('chart');\nconst chart = new Chart(el);\n// On cleanup:\nchart.destroy();\n// Properly releases references"
))

entries.append(g(
    "Using document.cookie for complex data",
    "document.cookie = 'cart=' + JSON.stringify(cartItems);",
    "// Use localStorage or sessionStorage for structured data:\nlocalStorage.setItem('cart', JSON.stringify(cartItems));\nconst cartItems = JSON.parse(localStorage.getItem('cart'));\n// 5MB limit vs 4KB for cookies, no bandwidth waste"
))

entries.append(g(
    "Not checking feature support",
    "element.animate([...]);\n// Web Animations API not supported in all browsers",
    "if ('animate' in Element.prototype) {\n  element.animate([...]);\n} else {\n  // Fallback: CSS classes or library\n  element.classList.add('animated');\n}"
))

entries.append(g(
    "Forcing layout thrashing",
    "for (let i = 0; i < items.length; i++) {\n  const div = document.createElement('div');\n  div.textContent = items[i];\n  container.appendChild(div);\n  const rect = div.getBoundingClientRect(); // forces layout\n  console.log(rect);\n}",
    "// Read all, then write all:\nconst fragment = document.createDocumentFragment();\nconst divs = items.map(item => {\n  const div = document.createElement('div');\n  div.textContent = item;\n  fragment.appendChild(div);\n  return div;\n});\ncontainer.appendChild(fragment);\nconst rects = divs.map(d => d.getBoundingClientRect());"
))

entries.append(g(
    "Using window.onload",
    "window.onload = function() {\n  initApp();\n};\n// Waits for all resources, fires once",
    "document.addEventListener('DOMContentLoaded', () => {\n  initApp();\n  // Fires when DOM is ready, before images/stylesheets\n});"
))

entries.append(g(
    "Not using IntersectionObserver for lazy loading",
    "window.addEventListener('scroll', () => {\n  images.forEach(img => {\n    if (img.getBoundingClientRect().top < window.innerHeight) {\n      img.src = img.dataset.src;\n    }\n  });\n});\n// Runs on every scroll event",
    "const observer = new IntersectionObserver((entries) => {\n  entries.forEach(entry => {\n    if (entry.isIntersecting) {\n      entry.target.src = entry.target.dataset.src;\n      observer.unobserve(entry.target);\n    }\n  });\n});\ndocument.querySelectorAll('[data-src]').forEach(img => observer.observe(img));"
))

entries.append(g(
    "Using jQuery for DOM queries",
    "$('.items').each(function() {\n  $(this).addClass('active');\n});\n// jQuery dependency",
    "document.querySelectorAll('.items').forEach(el => {\n  el.classList.add('active');\n});\n// Native DOM API is sufficient"
))

entries.append(g(
    "Not using custom properties (CSS variables)",
    ".primary { color: #333; }\n.secondary { color: #333; }\n.footer { color: #333; }\n// Repeated values",
    ":root {\n  --color-text: #333;\n}\n.primary, .secondary, .footer {\n  color: var(--color-text);\n}\n// Single source of truth, runtime modifiable"
))

# =============================================================================
# NODE.JS / BACKEND (15 entries)
# =============================================================================

entries.append(g(
    "Callback-based file read",
    "fs.readFile('config.json', 'utf8', (err, data) => {\n  if (err) throw err;\n  const config = JSON.parse(data);\n});",
    "const { readFile } = require('fs/promises');\nasync function loadConfig() {\n  const data = await readFile('config.json', 'utf8');\n  return JSON.parse(data);\n}"
))

entries.append(g(
    "Missing error handling in Express",
    "app.get('/api/data', (req, res) => {\n  const data = db.query();\n  res.json(data);\n});\n// If db.query() throws, unhandled rejection",
    "app.get('/api/data', async (req, res, next) => {\n  try {\n    const data = await db.query();\n    res.json(data);\n  } catch (err) {\n    next(err); // Pass to error handler\n  }\n});\n\napp.use((err, req, res, next) => {\n  console.error(err);\n  res.status(500).json({ error: 'Internal error' });\n});"
))

entries.append(g(
    "Process.exit() in server",
    "app.listen(3000, () => {\n  if (process.env.NODE_ENV === 'test') {\n    process.exit(0);\n  }\n});",
    "const server = app.listen(3000);\n// For tests:\nafter(() => server.close());\n// Don't call process.exit() - it kills pending requests"
))

entries.append(g(
    "Not handling unhandledRejection",
    "async function handler() {\n  await riskyOperation();\n}",
    "process.on('unhandledRejection', (reason, promise) => {\n  console.error('Unhandled Rejection at:', promise, 'reason:', reason);\n});\n// In Express with async handlers:\napp.use(async (req, res, next) => {\n  try { await next(); }\n  catch (err) { next(err); }\n});"
))

entries.append(g(
    "Blocking event loop in Node",
    "const hash = crypto.createHash('md5').update(bigBuffer).digest();\n// Synchronous crypto blocks the event loop",
    "// Use async crypto APIs:\nconst hash = await new Promise((resolve, reject) => {\n  const hash = crypto.createHash('sha256');\n  hash.update(bigBuffer);\n  resolve(hash.digest());\n});\n// Or worker thread for heavy computation"
))

entries.append(g(
    "Not using environment variables",
    "const PORT = 3000;\nconst DB_URL = 'mongodb://localhost/mydb';",
    "const PORT = process.env.PORT || 3000;\nconst DB_URL = process.env.DB_URL;\nif (!DB_URL) throw new Error('DB_URL is required');\n// Or with dotenv:\nrequire('dotenv').config();"
))

entries.append(g(
    "Memory leak with growing array",
    "const requestLog = [];\napp.use((req, res, next) => {\n  requestLog.push({ path: req.path, time: Date.now() });\n  next();\n});\n// Grows forever",
    "const requestLog = [];\nconst MAX_LOGS = 10000;\napp.use((req, res, next) => {\n  requestLog.push({ path: req.path, time: Date.now() });\n  if (requestLog.length > MAX_LOGS) {\n    requestLog.splice(0, requestLog.length - MAX_LOGS);\n  }\n  next();\n});// Or use a circular buffer / logging library"
))

entries.append(g(
    "Not streaming large responses",
    "app.get('/download', (req, res) => {\n  const data = fs.readFileSync('large-file.zip');\n  res.send(data);\n  // Loads entire file into memory",
    "app.get('/download', (req, res) => {\n  const stream = fs.createReadStream('large-file.zip');\n  res.setHeader('Content-Type', 'application/zip');\n  stream.pipe(res);\n  // Streams data without loading into memory\n});"
))

entries.append(g(
    "Missing body-parser size limit",
    "app.use(express.json());\n// Default: no limit",
    "app.use(express.json({ limit: '1mb' }));\napp.use(express.urlencoded({ extended: true, limit: '1mb' }));\n// Prevents DoS via large payloads"
))

entries.append(g(
    "Not using ESM modules",
    "const express = require('express');\nconst app = express();",
    "// package.json: { \"type\": \"module\" }\nimport express from 'express';\nimport { readFile } from 'node:fs/promises';\n// Native ESM - tree-shakeable, standard imports"
))

entries.append(g(
    "Process on uncaughtException",
    "process.on('uncaughtException', (err) => {\n  console.error(err);\n  // Server continues in unknown state\n});",
    "process.on('uncaughtException', (err) => {\n  console.error('Uncaught Exception:', err);\n  // Graceful shutdown:\n  server.close(() => process.exit(1));\n});\n// Better: fix the root cause, don't catch exceptions"
))

entries.append(g(
    "Not using clustering",
    "const express = require('express');\nconst app = express();\napp.listen(3000);",
    "const cluster = require('cluster');\nconst os = require('os');\nconst numCPUs = os.cpus().length;\n\nif (cluster.isPrimary) {\n  for (let i = 0; i < numCPUs; i++) cluster.fork();\n  cluster.on('exit', (worker, code) => cluster.fork());\n} else {\n  app.listen(3000);\n}"
))

entries.append(g(
    "Missing graceful shutdown",
    "const server = app.listen(3000);\n// No cleanup on SIGTERM",
    "const server = app.listen(3000);\n\nprocess.on('SIGTERM', () => {\n  console.log('SIGTERM received. Shutting down...');\n  server.close(() => {\n    // Close DB connections, finish requests\n    process.exit(0);\n  });\n});"
))

entries.append(g(
    "Not using health check endpoint",
    "const app = express();\napp.listen(3000);",
    "const app = express();\n\napp.get('/health', (req, res) => {\n  res.json({\n    status: 'ok',\n    uptime: process.uptime(),\n    timestamp: Date.now()\n  });\n});\n\napp.listen(3000);"
))

entries.append(g(
    "Global module state",
    "// server.js\nconst db = require('./db');\nconst app = require('./app')(db);\n// Shared state across hot-reloads in dev",
    "// Use dependency injection:\nconst createApp = (db) => {\n  const app = express();\n  app.use('/api', apiRoutes(db));\n  return app;\n};\n// Fresh state per test / reload"
))

# =============================================================================
# TESTING (10 entries)
# =============================================================================

entries.append(g(
    "Testing implementation details",
    "describe('Counter', () => {\n  it('increments', () => {\n    const counter = new Counter();\n    counter.inc();\n    expect(counter._value).toBe(1); // Testing private property\n  });\n});",
    "describe('Counter', () => {\n  it('increments', () => {\n    const counter = new Counter();\n    counter.inc();\n    expect(counter.getValue()).toBe(1); // Test public API\n  });\n});"
))

entries.append(g(
    "Async test without await",
    "it('fetches user', () => {\n  const user = fetchUser(1); // Returns Promise\n  expect(user.name).toBe('Alice'); // user is Promise, not object\n});",
    "it('fetches user', async () => {\n  const user = await fetchUser(1);\n  expect(user.name).toBe('Alice');\n});"
))

entries.append(g(
    "Testing with real HTTP server",
    "it('creates user', async () => {\n  const res = await fetch('http://localhost:3000/users', {\n    method: 'POST',\n    body: JSON.stringify({ name: 'Alice' })\n  });\n  // Hits real server - slow, fragile",
    "import { request } from 'supertest';\nimport app from '../app';\n\nit('creates user', async () => {\n  const res = await request(app)\n    .post('/users')\n    .send({ name: 'Alice' })\n    .expect(201);\n  expect(res.body.name).toBe('Alice');\n});"
))

entries.append(g(
    "Missing test cleanup",
    "beforeEach(() => {\n  fs.mkdirSync('/tmp/test');\n});\n// No cleanup - stale files between tests",
    "beforeEach(() => {\n  fs.mkdirSync('/tmp/test', { recursive: true });\n});\nafterEach(() => {\n  fs.rmSync('/tmp/test', { recursive: true, force: true });\n});"
))

entries.append(g(
    "Mocking the wrong thing",
    "jest.mock('fs');\n// Mocking the entire module",
    "jest.mock('fs', () => ({\n  ...jest.requireActual('fs'),\n  readFile: jest.fn().mockResolvedValue('{\"key\":\"value\"}')\n}));\n// Only mock what you need"
))

entries.append(g(
    "Flaky test with randomness",
    "it('generates token', () => {\n  const token = generateToken();\n  expect(token.length).toBe(32);\n  expect(token).toBe('abc...'); // Hardcoded expected value\n});",
    "it('generates token', () => {\n  const token = generateToken();\n  expect(token).toHaveLength(32);\n  expect(token).toMatch(/^[a-zA-Z0-9]+$/);\n  // Test properties, not exact values\n});"
))

entries.append(g(
    "Missing snapshot test for UI",
    "it('renders component', () => {\n  render(<UserCard user={user} />);\n  expect(screen.getByText('Alice')).toBeInTheDocument();\n  expect(screen.getByText('alice@example.com')).toBeInTheDocument();\n  // Breaks when layout changes",
    "it('renders user card', () => {\n  const { container } = render(<UserCard user={user} />);\n  expect(container).toMatchSnapshot();\n  // Captures full render, catches unintended changes\n});"
))

entries.append(g(
    "Testing without isolation",
    "it('creates user', () => {\n  db.insert({ name: 'Alice' });\n});\nit('lists users', () => {\n  const users = db.findAll();\n  expect(users).toHaveLength(1); // Depends on previous test\n});",
    "beforeEach(() => db.clear());\nafterEach(() => db.clear());\n\nit('creates user', () => {\n  db.insert({ name: 'Alice' });\n});\nit('lists users', () => {\n  db.insert({ name: 'Alice' });\n  const users = db.findAll();\n  expect(users).toHaveLength(1); // Independent\n});"
))

entries.append(g(
    "Using console.log for debugging in tests",
    "it('should work', () => {\n  const result = process(input);\n  console.log(result); // Debug output\n  expect(result).toBe(true);\n});",
    "it('should work', () => {\n  const result = process(input);\n  expect(result).toBe(true);\n});\n// Use jest.spyOn for inspecting behavior:\nconst spy = jest.spyOn(module, 'func');\n// Spy is auto-restored after test"
))

entries.append(g(
    "Not testing error cases",
    "it('parses JSON', () => {\n  expect(parse('{}')).toEqual({});\n  // Only tests the happy path\n});",
    "describe('parse', () => {\n  it('parses valid JSON', () => {\n    expect(parse('{}')).toEqual({});\n  });\n  it('throws on invalid input', () => {\n    expect(() => parse('invalid')).toThrow(SyntaxError);\n  });\n  it('handles empty string', () => {\n    expect(() => parse('')).toThrow(SyntaxError);\n  });\n});"
))

# =============================================================================
# BUILD TOOLS & MODULES (10 entries)
# =============================================================================

entries.append(g(
    "Missing .gitignore",
    "node_modules/\npackage.json\napp.js",
    "# .gitignore\nnode_modules/\n*.log\n.env\n.DS_Store\ndist/\ncoverage/\n*.tgz\n# Never commit node_modules, env files, or build artifacts"
))

entries.append(g(
    "Not using package.json scripts",
    "node build.js\nnpx jest\nnpx eslint src/",
    "# package.json\n{\n  \"scripts\": {\n    \"build\": \"node build.js\",\n    \"test\": \"jest\",\n    \"lint\": \"eslint src/\",\n    \"dev\": \"nodemon src/index.js\"\n  }\n}"
))

entries.append(g(
    "Missing TypeScript config",
    "// Using JSDoc comments for types\n// No type checking",
    "# tsconfig.json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"ESNext\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"outDir\": \"dist\",\n    \"rootDir\": \"src\",\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"src\"]\n}"
))

entries.append(g(
    "Not using source maps",
    "webpack config with minification only\n// Production errors show minified code",
    "// webpack.config.js\nmodule.exports = {\n  devtool: 'source-map', // For production\n  // 'eval-source-map' for dev (faster)\n  // 'inline-source-map' embeds in bundle\n  mode: 'production'\n}"
))

entries.append(g(
    "Missing .eslintrc",
    "// No linting",
    "# .eslintrc.json\n{\n  \"env\": { \"browser\": true, \"es2022\": true, \"node\": true },\n  \"extends\": \"eslint:recommended\",\n  \"rules\": {\n    \"no-unused-vars\": \"error\",\n    \"no-console\": \"warn\",\n    \"prefer-const\": \"error\"\n  }\n}"
))

entries.append(g(
    "Using CommonJS in ESM project",
    "// package.json has \"type\": \"module\"\nconst fs = require('fs'); // Error!",
    "// package.json has \"type\": \"module\"\nimport fs from 'node:fs';\nimport { readFile } from 'node:fs/promises';\n// Or use createRequire for CJS-only packages:\nimport { createRequire } from 'module';\nconst require = createRequire(import.meta.url);\nconst lodash = require('lodash');"
))

entries.append(g(
    "Not bundling for browser",
    "<script src=\"node_modules/lodash/lodash.js\"></script>\n<script src=\"app.js\"></script>\n// Individual script tags, no module resolution",
    "// Use a bundler:\n// npx esbuild app.js --bundle --outfile=dist/bundle.js\n// Or vite:\n// npx vite build\n// Bundling: resolves imports, minifies, tree-shakes"
))

entries.append(g(
    "Missing dependency pinning",
    "{\n  \"dependencies\": {\n    \"express\": \"^4.18.0\"\n  }\n}",
    "{\n  \"dependencies\": {\n    \"express\": \"4.18.2\"\n  },\n  \"devDependencies\": {\n    \"jest\": \"29.7.0\",\n    \"eslint\": \"8.56.0\"\n  }\n}\n# Pin versions for reproducible builds\n# Use package-lock.json / yarn.lock"
))

entries.append(g(
    "Not using tree shaking",
    "// Webpack config missing sideEffects flag",
    "// package.json\n{\n  \"sideEffects\": false\n}\n// Tells bundler all modules are pure - safe to tree-shake\n// Or list files with side effects:\n// \"sideEffects\": [\"*.css\", \"./src/polyfills.js\"]"
))

entries.append(g(
    "Missing pre/post build scripts",
    "{\n  \"scripts\": {\n    \"build\": \"webpack\"\n  }\n}",
    "{\n  \"scripts\": {\n    \"prebuild\": \"npm run lint\",\n    \"build\": \"webpack --mode production\",\n    \"postbuild\": \"npm run test\"\n  }\n}\n# Pre/post scripts run automatically"
))

# =============================================================================
# FUNCTIONAL PATTERN / IMMUTABILITY (10 entries)
# =============================================================================

entries.append(g(
    "Mutating array in-place",
    "const items = [1, 2, 3];\nitems.push(4);\nitems.filter(x => x > 2);\n// Original modified",
    "const items = [1, 2, 3];\nconst withNew = [...items, 4];\nconst filtered = items.filter(x => x > 2);\n// Original unchanged - predictable state"
))

entries.append(g(
    "Mutating object in-place",
    "const config = { host: 'localhost', port: 3000 };\nconfig.port = 8080;\n// Side effect",
    "const config = { host: 'localhost', port: 3000 };\nconst updated = { ...config, port: 8080 };\n// New object, original unchanged"
))

entries.append(g(
    "Side effects in pure function",
    "function calculateTotal(items) {\n  const total = items.reduce((s, i) => s + i.price, 0);\n  logTransaction(total); // side effect\n  return total;\n}",
    "function calculateTotal(items) {\n  return items.reduce((s, i) => s + i.price, 0);\n}\n// Separate pure calculation from side effects:\nconst total = calculateTotal(items);\nlogTransaction(total);"
))

entries.append(g(
    "Not using Array.prototype methods",
    "const result = [];\nfor (let i = 0; i < arr.length; i++) {\n  if (arr[i] > 0) result.push(arr[i] * 2);\n}",
    "const result = arr.filter(x => x > 0).map(x => x * 2);\n// Composable, declarative, no mutation"
))

entries.append(g(
    "Using Object.assign for deep merge",
    "const merged = Object.assign({}, defaults, overrides);\n// Shallow merge - nested objects replaced",
    "// Deep merge utility:\nfunction deepMerge(target, ...sources) {\n  return sources.reduce((acc, source) => {\n    for (const key of Object.keys(source)) {\n      if (source[key] && typeof source[key] === 'object') {\n        acc[key] = deepMerge(acc[key] || {}, source[key]);\n      } else {\n        acc[key] = source[key];\n      }\n    }\n    return acc;\n  }, { ...target });\n}"
))

entries.append(g(
    "Missing immutability helper",
    "state.items[index].name = newName;\n// Direct mutation of nested state",
    "const newState = {\n  ...state,\n  items: state.items.map((item, i) =>\n    i === index ? { ...item, name: newName } : item\n  )\n};\n// Or use Immer for simpler mutable-style updates"
))

entries.append(g(
    "Function without default parameters",
    "function createUrl(host, port, path) {\n  host = host || 'localhost';\n  port = port || 80;\n  path = path || '/';\n  return `http://${host}:${port}${path}`;\n}",
    "function createUrl(host = 'localhost', port = 80, path = '/') {\n  return `http://${host}:${port}${path}`;\n}\n// Default parameters handle undefined explicitly"
))

entries.append(g(
    "Not using curry for partial application",
    "function add(a, b) { return a + b; }\nconst add5 = (x) => add(5, x);\nconst add10 = (x) => add(10, x);",
    "const add = (a) => (b) => a + b;\nconst add5 = add(5);\nconst add10 = add(10);\n// Curried functions enable partial application"
))

entries.append(g(
    "Using reduce for multiple operations",
    "const doubled = arr.map(x => x * 2);\nconst filtered = doubled.filter(x => x > 10);\nconst summed = filtered.reduce((a, b) => a + b, 0);\n// Multiple array traversals",
    "// Single traversal:\nconst summed = arr.reduce((acc, x) => {\n  const doubled = x * 2;\n  if (doubled > 10) acc += doubled;\n  return acc;\n}, 0);\n// Or use pipeline for readability:\nimport pipe from 'lodash/fp/flow';\nconst pipeline = pipe(map(double), filter(gt(10)), sum);"
))

entries.append(g(
    "Not using pipeline composition",
    "const result = capitalize(trim(toLower(input)));",
    "// Right-to-left composition (lodash fp):\nimport { flow, toLower, trim, capitalize } from 'lodash/fp';\nconst normalize = flow(toLower, trim, capitalize);\nconst result = normalize(input);\n// Left-to-right (native):\nconst normalize = pipe(toLower, trim, capitalize);"
))

# =============================================================================
# SAVE
# =============================================================================

random.seed(42)
random.shuffle(entries)

os.makedirs(BASE, exist_ok=True)
out = BASE / "dev_finetuning_javascript.json"
with open(out, 'w') as f:
    json.dump(entries, f, indent=2)

# Count by tag category
categories = {}
for e in entries:
    for tag in e['tags']:
        if tag != 'javascript':
            categories[tag] = categories.get(tag, 0) + 1

print(f"Total entries: {len(entries)}")
print(f"Categories:")
for cat, count in sorted(categories.items(), key=lambda x: -x[1]):
    print(f"  {cat}: {count}")
print(f"Saved: {out}")