// Projects — model, priority and physics-based bubble layout.
// UI strings are resolved live via window.t(), independent of the agenda's CATEGORIES system.

const projClamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
const projEsc = s => String(s ?? '').replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));

const PROJ_PRIORITY_SPEC = {
  high:   { w:1.00, size:152, color:'rgba(210,70,70,.55)' },
  orange: { w:0.78, size:130, color:'rgba(210,130,50,.5)' },
  yellow: { w:0.58, size:114, color:'rgba(190,160,30,.5)' },
  cyan:   { w:0.40, size:98,  color:'rgba(50,155,215,.5)' },
  violet: { w:0.22, size:84,  color:'rgba(115,85,215,.5)' },
};
// Backwards-compatible alias with translated labels resolved live (key = priority).
const PROJ_PRIORITY = new Proxy({}, {
  get(_t, key) {
    const spec = PROJ_PRIORITY_SPEC[key];
    if (!spec) return undefined;
    return { ...spec, label: t('proj.priority.' + key) };
  },
});
const PROJ_PRIORITY_ORDER = ['high','orange','yellow','cyan','violet'];
const PROJ_DAY = 864e5;

/* ── model ── */
class Project {
  constructor(d = {}) {
    this.id        = d.id || Project.uid();
    this.name      = d.name || t('proj.defaultName');
    this.priority  = PROJ_PRIORITY_SPEC[d.priority] ? d.priority : 'cyan';
    this.progress  = projClamp(d.progress ?? 0, 0, 100);
    this.state     = d.state || t('proj.defaultState');
    this.note      = d.note || '';
    this.todos     = Array.isArray(d.todos)
      ? d.todos.map(t2 => ({ id: t2.id || Project.uid(), text: t2.text || '', done: !!t2.done }))
      : [];
    this.lastUpdate = d.lastUpdate || null;
    this.createdAt  = d.createdAt || new Date().toISOString();
    // physics state (in-memory only)
    this.x = 0; this.y = 0; this.vx = 0; this.vy = 0; this._seeded = false;
  }
  static uid() { return 'p' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }

  get spec()   { return PROJ_PRIORITY_SPEC[this.priority]; }
  get size()   { return this.spec.size; }
  get radius() { return this.size / 2; }
  get color()  { return this.spec.color; }
  get isStale() {
    return this.lastUpdate &&
           (Date.now() - new Date(this.lastUpdate)) > 14 * PROJ_DAY;
  }
  prominence() {
    let p = this.spec.w;
    if (this.lastUpdate) {
      const days = (Date.now() - new Date(this.lastUpdate)) / PROJ_DAY;
      p += projClamp(0.2 * (1 - days / 14), 0, 0.2);   // recent → closer to center
    }
    return projClamp(p, 0, 1);
  }
  touch() { this.lastUpdate = new Date().toISOString(); }
  toJSON() {
    return {
      id: this.id, name: this.name, priority: this.priority,
      progress: this.progress, state: this.state, note: this.note,
      todos: this.todos, lastUpdate: this.lastUpdate, createdAt: this.createdAt,
    };
  }
}

/* ── organic force-directed layout ── */
class ForceLayout {
  constructor({ iters = 600 } = {}) { this.iters = iters; }
  run(projects, W, H) {
    if (!projects.length) return;
    const cx = W / 2, cy = H / 2;
    const maxR = Math.min(W, H) * 0.40;
    const margin = 16, topPad = 70, pad = 10;

    projects.forEach((p, i) => {
      if (!p._seeded) {
        const a = i * 2.399963229;                       // golden angle
        const rr = maxR * Math.sqrt((i + 0.5) / projects.length);
        p.x = cx + rr * Math.cos(a);
        p.y = cy + rr * Math.sin(a);
        p._seeded = true;
      }
      p.vx = 0; p.vy = 0;
    });

    for (let it = 0; it < this.iters; it++) {
      // pull toward target radius based on prominence
      for (const p of projects) {
        let dx = p.x - cx, dy = p.y - cy;
        const d = Math.hypot(dx, dy) || 0.001;
        const target = (1 - p.prominence()) * maxR;
        const f = (d - target) * 0.015;
        p.vx -= (dx / d) * f; p.vy -= (dy / d) * f;
      }
      // repel to avoid overlap
      for (let i = 0; i < projects.length; i++) {
        for (let j = i + 1; j < projects.length; j++) {
          const a = projects[i], b = projects[j];
          let dx = b.x - a.x, dy = b.y - a.y;
          const d = Math.hypot(dx, dy) || 0.001;
          const min = a.radius + b.radius + margin;
          if (d < min) {
            const push = (min - d) / d * 0.5;
            const ox = dx * push, oy = dy * push;
            a.x -= ox; a.y -= oy; b.x += ox; b.y += oy;
          }
        }
      }
      // integrate + damp + contain
      for (const p of projects) {
        p.x += p.vx; p.y += p.vy; p.vx *= 0.82; p.vy *= 0.82;
        const r = p.radius;
        p.x = projClamp(p.x, r + pad, W - r - pad);
        p.y = projClamp(p.y, r + topPad, H - r - pad);
      }
    }
  }
}

Object.assign(window, {
  Project, ForceLayout,
  PROJ_PRIORITY, PROJ_PRIORITY_SPEC, PROJ_PRIORITY_ORDER,
  projClamp, projEsc,
});
