// ============================================================
// One Roof Labs — sample roof intelligence dataset
// Deterministic procedural city (Colorado Springs demo market).
// ============================================================

function makeRNG(seed) {
  let s = seed >>> 0;
  return function () {
    s = (s * 1664525 + 1013904223) >>> 0;
    return s / 4294967296;
  };
}

const STREETS = ['Cascade', 'Tejon', 'Nevada', 'Weber', 'Wahsatch', 'Wood', 'Pikes Peak',
  'Bijou', 'Platte', 'Boulder', 'Kiowa', 'Willamette', 'Cucharras', 'Vermijo', 'Costilla',
  'Monument', 'Acacia', 'Dale', 'Caramillo', 'Fontanero'];
const SUFFIX = ['Ave', 'St', 'Blvd', 'Dr', 'Way', 'Pl'];
const FIRST = ['Marsha', 'Dale', 'Karen', 'Robert', 'Linda', 'Greg', 'Susan', 'Frank',
  'Diane', 'Carl', 'Joan', 'Ted', 'Nancy', 'Phil', 'Janet', 'Roy', 'Cindy', 'Walt', 'Brenda', 'Hank'];
const LAST = ['Halvorsen', 'Kowalski', 'Reyes', 'Underwood', 'Brandt', 'Coombs', 'Tran',
  'Ferris', 'Okafor', 'Delgado', 'Whitlock', 'Bauer', 'Mancuso', 'Pruitt', 'Esparza',
  'Lindgren', 'Vargas', 'Holloway', 'Stenson', 'Mireles'];
const MATERIALS = ['Asphalt 3-tab', 'Architectural shingle', 'Wood shake', 'Concrete tile', 'Standing-seam metal'];
const DMG_HIGH = ['Hail impact — fracturing', 'Wind uplift — tab loss', 'End-of-life granule loss', 'Ridge sag + deck deflection'];
const DMG_MED = ['Hail bruising — moderate', 'Granule loss — accelerated', 'Flashing failure', 'Wind creasing'];
const DMG_LOW = ['Early granule wear', 'Isolated hail spatter', 'Minor flashing wear', 'Surface weathering'];
const DMG_OK = ['No actionable damage', 'Within service life', 'Recently replaced', 'Sound — monitor'];

function scoreBucket(score) {
  if (score >= 75) return 'high';
  if (score >= 50) return 'med';
  if (score >= 25) return 'low';
  return 'ok';
}
function dmgFor(score, rng) {
  const b = scoreBucket(score);
  const pool = b === 'high' ? DMG_HIGH : b === 'med' ? DMG_MED : b === 'low' ? DMG_LOW : DMG_OK;
  return pool[Math.floor(rng() * pool.length)];
}

// ---- Rotated roof geometry ----
function rotPt(cx, cy, dx, dy, ang) {
  const c = Math.cos(ang), s = Math.sin(ang);
  return [cx + dx * c - dy * s, cy + dx * s + dy * c];
}
function localShape(type, w, h) {
  const hw = w / 2, hh = h / 2;
  let poly = [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]];
  const ridges = [];
  if (type === 'gable') ridges.push([[-hw, 0], [hw, 0]]);
  else if (type === 'gableP') ridges.push([[0, -hh], [0, hh]]);
  else if (type === 'hip') {
    const r = hw * 0.42;
    ridges.push([[-r, 0], [r, 0]], [[-hw, -hh], [-r, 0]], [[hw, -hh], [r, 0]],
      [[-hw, hh], [-r, 0]], [[hw, hh], [r, 0]]);
  } else if (type === 'pyramid') {
    ridges.push([[-hw, -hh], [hw, hh]], [[hw, -hh], [-hw, hh]]);
  } else if (type === 'L') {
    poly = [[-hw, -hh], [hw * 0.2, -hh], [hw * 0.2, 0], [hw, 0], [hw, hh], [-hw, hh]];
    ridges.push([[-hw * 0.4, -hh], [-hw * 0.4, hh]], [[hw * 0.2, hh * 0.5], [hw, hh * 0.5]]);
  }
  return { poly, ridges };
}
function houseGeoRot(cx, cy, w, h, ang, type) {
  const ls = localShape(type, w, h);
  return {
    poly: ls.poly.map(([dx, dy]) => rotPt(cx, cy, dx, dy, ang)),
    ridges: ls.ridges.map((seg) => seg.map(([dx, dy]) => rotPt(cx, cy, dx, dy, ang))),
  };
}

const ROAD_NAMES = ['Crown Ridge Dr', 'Panorama Dr', 'Pioneer Ln', 'Paradise Ln', 'Westmoor Dr',
  'Garden Hill Dr', 'Castle Rd', 'Valley Rd', 'Echo Ln', 'Pleasant St', 'Glen Eyrie Cir',
  'King St', 'Willamette Ave', 'Friendship Ln', 'Crescent Ln', 'Royalty Ct', 'Camelot Ct',
  'Mesa View Dr', 'Chambers Dr', 'Pikes Peak Ave'];

function streetPath(pts) {
  return pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');
}

// Generate a realistic residential neighborhood: curved named streets,
// parks, and houses set along each road (roof outlined by damage score).
function generateCity(seed = 7, VW = 2400, VH = 1500) {
  const rng = makeRNG(seed);
  const roofs = [], streets = [], parks = [];
  let id = 1;

  parks.push({ x: VW - 400, y: 60, w: 360, h: 270, name: 'Mesa Wildlife Preserve' });
  const inPark = (x, y) => parks.some((p) => x > p.x - 12 && x < p.x + p.w + 12 && y > p.y - 12 && y < p.y + p.h + 12);

  // curved vertical arterials + connectors
  const vroads = [];
  const nV = 3;
  for (let i = 1; i <= nV; i++) {
    const x0 = VW * i / (nV + 1) + (rng() - 0.5) * 140;
    const a = 40 + rng() * 70, p = 460 + rng() * 320, ph = rng() * 6;
    const fn = (y) => x0 + a * Math.sin(y / p + ph);
    const pts = []; for (let y = 0; y <= VH; y += 26) pts.push([fn(y), y]);
    const width = i === 2 ? 17 : 11;
    vroads.push({ fn, width });
    streets.push({ d: streetPath(pts), name: ROAD_NAMES[(i * 7) % ROAD_NAMES.length], width, kind: i === 2 ? 'arterial' : 'res', vert: true });
  }
  const nearV = (x, y) => vroads.some((v) => Math.abs(x - v.fn(y)) < v.width / 2 + 26);

  // curved horizontal residential streets
  const hs = [];
  const rowGap = 150;
  for (let y = 120; y < VH - 50; y += rowGap) {
    const a = 18 + rng() * 54, p = 620 + rng() * 520, ph = rng() * 6;
    const fn = (x) => y + a * Math.sin(x / p + ph);
    const slope = (x) => a * Math.cos(x / p + ph) / p;
    const pts = []; for (let x = 0; x <= VW; x += 22) pts.push([x, fn(x)]);
    const name = ROAD_NAMES[(hs.length * 3 + 4) % ROAD_NAMES.length];
    hs.push({ fn, slope, name });
    streets.push({ d: streetPath(pts), name, width: 10, kind: 'res' });
  }

  // houses along both sides of each street, placed once per street with
  // a collision check so none overlap
  const placed = []; // {cx, cy, rad}
  const fits = (cx, cy, rad) => {
    for (const o of placed) {
      const dx = cx - o.cx, dy = cy - o.cy;
      if (dx * dx + dy * dy < (rad + o.rad) * (rad + o.rad)) return false;
    }
    return true;
  };
  for (let bi = 0; bi < hs.length; bi++) {
    const s = hs[bi];
    const lotW = 50;
    for (let x = 44; x < VW - 22; x += lotW) {
      for (let wi = 0; wi < 2; wi++) {
        const yc = s.fn(x);
        if (nearV(x, yc) || inPark(x, yc)) continue;
        if (rng() > 0.84) continue;
        const ang = Math.atan(s.slope(x));
        const nx = -Math.sin(ang), ny = Math.cos(ang);
        const dir = wi === 0 ? 1 : -1;
        const hw = 30 + rng() * 13;
        const hh = 22 + rng() * 9;
        const off = (18 + rng() * 8) + hh / 2;
        const cx = x + nx * dir * off;
        const cy = yc + ny * dir * off;
        if (cy < 60 || cy > VH - 40 || cx < 30 || cx > VW - 30) continue;
        if (inPark(cx, cy)) continue;
        const rad = Math.max(hw, hh) / 2 + 4; // collision radius + gap
        if (!fits(cx, cy, rad)) continue;
        placed.push({ cx, cy, rad });
        const types = ['gable', 'gable', 'gable', 'hip', 'gableP', 'pyramid', 'L'];
        const type = types[Math.floor(rng() * types.length)];
        const geo = houseGeoRot(cx, cy, hw, hh, ang, type);
        const u = rng();
        const score = u < 0.45 ? Math.floor(rng() * 25)
          : u < 0.75 ? 25 + Math.floor(rng() * 25)
          : u < 0.92 ? 50 + Math.floor(rng() * 25)
          : 75 + Math.floor(rng() * 26);
        const bucket = scoreBucket(score);
        const area = 1200 + Math.floor(rng() * 2400);
        const base = bucket === 'high' ? 16500 : bucket === 'med' ? 13500 : bucket === 'low' ? 11000 : 9500;
        const cost = Math.round((base + area * (2.4 + rng() * 1.6)) / 50) * 50;
        const num = 100 + Math.floor(rng() * 4800);
        const owner = FIRST[Math.floor(rng() * FIRST.length)] + ' ' + LAST[Math.floor(rng() * LAST.length)];
        const day = 1 + Math.floor(rng() * 28);
        roofs.push({
          id: id++, x: cx - hw / 2, y: cy - hh / 2, w: hw, h: hh, cx, cy,
          poly: geo.poly, ridges: geo.ridges, type, score, bucket, cost,
          address: num + ' ' + s.name, owner,
          material: MATERIALS[Math.floor(rng() * MATERIALS.length)],
          age: 8 + Math.floor(rng() * 22), area, damage: dmgFor(score, rng),
          scanDate: '2026-05-' + String(day).padStart(2, '0'),
          ridgeSag: bucket === 'high' && rng() > 0.4, deckDefl: bucket === 'high' && rng() > 0.6,
        });
      }
    }
  }
  return { roofs, streets, parks, VW, VH };
}

function polyPoints(poly) { return poly.map((p) => p[0].toFixed(1) + ',' + p[1].toFixed(1)).join(' '); }

const SCORE_COLORS = {
  high: 'oklch(0.63 0.18 28)',
  med:  'oklch(0.74 0.14 62)',
  low:  'oklch(0.83 0.13 92)',
  ok:   'oklch(0.70 0.11 150)',
};
const SCORE_LABELS = {
  high: 'Immediate replacement',
  med:  'Near-term',
  low:  'Monitor',
  ok:   'Healthy',
};

const STATS = {
  scanned: 165000,
  threshold: 41250,
  jobs5pct: 2063,
  avgJob: 13500,
  revenue: 27.8,
  rmse: 3,
  ptsPerM2: 450,
  hours: 36,
  kmPerDay: 100,
  markets: 4,
};

Object.assign(window, {
  makeRNG, generateCity, houseGeoRot, polyPoints, scoreBucket, SCORE_COLORS, SCORE_LABELS, STATS,
});
