// "Projects" view — "liquid glass" bubble whiteboard.
// Bubble/Board are vanilla JS (imperative DOM) mounted inside a React wrapper
// (ProjectsView), because the physics and animations are already proven and
// rewriting them in idiomatic React wouldn't add anything.

const { useEffect, useRef: projUseRef } = React;

/* ── single bubble view ── */
class ProjBubble {
  constructor(project, board) {
    this.p = project;
    this.board = board;
    this.expanded = false;
    this.draft = null;

    const el = document.createElement('div');
    el.id = 'proj-nd-' + project.id;
    el.style.animationDelay = (Math.random() * 3).toFixed(2) + 's';
    el.addEventListener('click', () => { if (!this.expanded) this.expand(); });
    this.el = el;

    this.renderIdle();
    this.place();
  }

  ringSVG() {
    const px = this.p.size, tot = px + 14, r = tot / 2 - 5;
    const c = 2 * Math.PI * r, off = c - (this.p.progress / 100) * c;
    return `<svg class="proj-ring" width="${tot}" height="${tot}" xmlns="http://www.w3.org/2000/svg">
      <circle cx="${tot/2}" cy="${tot/2}" r="${r}" fill="none" stroke="rgba(0,0,0,.07)" stroke-width="3"/>
      <circle cx="${tot/2}" cy="${tot/2}" r="${r}" fill="none" stroke="${this.p.color}" stroke-width="3"
        stroke-dasharray="${c.toFixed(2)}" stroke-dashoffset="${off.toFixed(2)}"
        stroke-linecap="round" opacity=".65"/>
    </svg>`;
  }

  // rebuilds idle content (not called while expanded)
  renderIdle() {
    const p = this.p, px = p.size;
    this.el.className = `proj-node proj-p-${p.priority}` + (px >= 130 ? ' proj-big' : '') + (p.isStale ? ' proj-node-stale' : '');
    this.el.style.width = px + 'px';
    this.el.style.height = px + 'px';
    this.el.innerHTML =
      this.ringSVG() +
      `<div class="proj-idle">
        <div class="proj-idle-name">${projEsc(p.name)}</div>
        <div class="proj-idle-pct">${p.progress}%</div>
        <div class="proj-idle-state">${projEsc(p.state)}</div>
      </div>
      <div class="proj-panel"></div>`;
  }

  place() {
    if (this.expanded) {
      const r = this.board.map.getBoundingClientRect();
      this.el.style.left = r.width / 2 + 'px';
      this.el.style.top  = r.height / 2 + 'px';
    } else {
      this.el.style.left = this.p.x + 'px';
      this.el.style.top  = this.p.y + 'px';
    }
  }

  expand() {
    this.board.collapseAll();
    this.expanded = true;
    this.draft = {
      name: this.p.name, priority: this.p.priority, progress: this.p.progress,
      state: this.p.state, note: this.p.note,
      todos: this.p.todos.map(t => ({ ...t })),
    };
    this.el.classList.add('proj-expanded');
    this.buildPanel();
    this.place();
    this.board.setOpen(this);
  }

  collapse() {
    if (!this.expanded) return;
    this.expanded = false;
    this.draft = null;
    this.el.classList.remove('proj-expanded');
    const pan = this.el.querySelector('.proj-panel');
    if (pan) pan.innerHTML = '';
    this.place();
  }

  buildPanel() {
    const d = this.draft;
    const color = PROJ_PRIORITY_SPEC[d.priority].color;
    const pan = this.el.querySelector('.proj-panel');
    pan.innerHTML = `
      <div class="proj-pan-header">
        <input class="proj-pan-title-input" value="${projEsc(d.name)}" placeholder="${projEsc(t('proj.panel.namePlaceholder'))}">
        <button class="proj-pan-close" title="${projEsc(t('proj.panel.close'))}"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
      </div>
      <div class="proj-pan-scroll">
        <div>
          <div class="proj-pan-label">${projEsc(t('proj.panel.priorityLabel'))}</div>
          <div class="proj-pills proj-prio-pills">
            ${PROJ_PRIORITY_ORDER.map(k => `<button class="proj-pill proj-prio${d.priority===k?' proj-on':''}" data-k="${k}"><span class="proj-dot" style="background:${PROJ_PRIORITY_SPEC[k].color}"></span>${projEsc(t('proj.priority.' + k))}</button>`).join('')}
          </div>
        </div>
        <div>
          <div class="proj-pan-label">${projEsc(t('proj.panel.progressLabel'))} <span class="proj-prog-num">${d.progress}%</span></div>
          <input type="range" class="proj-pslider" min="0" max="100" step="1" value="${d.progress}"
                 style="--p:${d.progress}; --c:${color}">
        </div>
        <div>
          <div class="proj-pan-label">${projEsc(t('proj.panel.stateLabel'))}</div>
          <input class="proj-pan-input proj-state-input" value="${projEsc(d.state)}" placeholder="${projEsc(t('proj.panel.statePlaceholder'))}">
        </div>
        <div>
          <div class="proj-pan-label">${projEsc(t('proj.panel.todosLabel'))}</div>
          <div class="proj-todos"></div>
          <div class="proj-todo-add">
            <input class="proj-todo-input" placeholder="${projEsc(t('proj.panel.todoPlaceholder'))}">
            <button class="proj-todo-add-btn" title="${projEsc(t('proj.panel.addTodo'))}">+</button>
          </div>
        </div>
        <div>
          <div class="proj-pan-label">${projEsc(t('proj.panel.notesLabel'))}</div>
          <textarea class="proj-pan-area proj-note-area" placeholder="${projEsc(t('proj.panel.notesPlaceholder'))}">${projEsc(d.note)}</textarea>
        </div>
      </div>
      <div class="proj-pan-actions">
        <button class="proj-btn-del">${projEsc(t('proj.panel.delete'))}</button>
        <button class="proj-btn-save">${projEsc(t('proj.panel.save'))}</button>
      </div>`;

    this.bindPanel(pan);
    this.renderTodos(pan);
  }

  bindPanel(pan) {
    const d = this.draft;
    const stop = e => e.stopPropagation();
    pan.addEventListener('click', stop);   // clicks inside the panel never close the bubble

    pan.querySelector('.proj-pan-title-input').addEventListener('input', e => { d.name = e.target.value; });

    // priority
    pan.querySelectorAll('.proj-prio-pills .proj-pill').forEach(b => b.addEventListener('click', () => {
      d.priority = b.dataset.k;
      pan.querySelectorAll('.proj-prio-pills .proj-pill').forEach(x => x.classList.toggle('proj-on', x === b));
      const slider = pan.querySelector('.proj-pslider');
      slider.style.setProperty('--c', PROJ_PRIORITY_SPEC[d.priority].color);
    }));

    // progress (single slider)
    const slider = pan.querySelector('.proj-pslider');
    const num = pan.querySelector('.proj-prog-num');
    slider.addEventListener('input', () => {
      d.progress = +slider.value;
      slider.style.setProperty('--p', d.progress);
      num.textContent = d.progress + '%';
    });

    // state (free text, manual)
    pan.querySelector('.proj-state-input').addEventListener('input', e => { d.state = e.target.value; });

    pan.querySelector('.proj-note-area').addEventListener('input', e => { d.note = e.target.value; });

    // tasks
    const input = pan.querySelector('.proj-todo-input');
    const addTodo = () => {
      const text = input.value.trim();
      if (!text) return;
      d.todos.push({ id: Project.uid(), text, done: false });
      input.value = '';
      this.renderTodos(pan);
      input.focus();
    };
    pan.querySelector('.proj-todo-add-btn').addEventListener('click', addTodo);
    input.addEventListener('keydown', e => { if (e.key === 'Enter') addTodo(); });

    pan.querySelector('.proj-pan-close').addEventListener('click', () => this.board.collapseAll());
    pan.querySelector('.proj-btn-del').addEventListener('click', () => this.board.remove(this.p.id));
    pan.querySelector('.proj-btn-save').addEventListener('click', () => this.save());
  }

  renderTodos(pan) {
    const d = this.draft;
    const box = pan.querySelector('.proj-todos');
    if (!d.todos.length) { box.innerHTML = `<div class="proj-todo-empty">${projEsc(t('proj.panel.todosEmpty'))}</div>`; return; }
    box.innerHTML = d.todos.map(td => `
      <div class="proj-todo${td.done ? ' proj-done' : ''}" data-id="${td.id}">
        <button class="proj-todo-check">${td.done ? '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>' : ''}</button>
        <span class="proj-todo-text">${projEsc(td.text)}</span>
        <button class="proj-todo-del" title="${projEsc(t('proj.panel.delete'))}"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
      </div>`).join('');
    box.querySelectorAll('.proj-todo').forEach(row => {
      const id = row.dataset.id;
      const todo = d.todos.find(td => td.id === id);
      row.querySelector('.proj-todo-check').addEventListener('click', () => {
        todo.done = !todo.done;
        this.renderTodos(pan);
      });
      row.querySelector('.proj-todo-del').addEventListener('click', () => {
        d.todos = d.todos.filter(td => td.id !== id);
        this.renderTodos(pan);
      });
    });
  }

  save() {
    const p = this.p, d = this.draft;
    p.name = d.name.trim() || t('proj.untitled');
    p.priority = d.priority;
    p.progress = d.progress;
    p.state = d.state.trim() || t('proj.defaultState');
    p.note = d.note;
    p.todos = d.todos;
    p.touch();
    this.board.collapseAll();
    this.board.relayoutAndRender();
    this.board.persist();
  }
}

/* ── orchestrator ── */
class ProjBoard {
  constructor(container) {
    this.store = createStore({
      apiPath: 'projects',
      storageKey: 'projects.items.v1',
      metaKey: 'projects.meta.v1',
      demoFactory: () => [],
    });
    this.layout = new ForceLayout();
    this.projects = [];
    this.bubbles = new Map();
    this.openBubble = null;

    this.container = container;
    this.map = container.querySelector('#proj-map');
    this.dimEl = container.querySelector('#proj-dim');
    this.addBtn = container.querySelector('#proj-add');

    this._onDimClick = () => this.collapseAll();
    this._onKeydown = e => { if (e.key === 'Escape') this.collapseAll(); };
    this._onAddClick = () => this.add();
    this._onResize = () => {
      clearTimeout(this._resizeT);
      this._resizeT = setTimeout(() => this.relayoutAndRender(), 120);
    };

    this.dimEl.addEventListener('click', this._onDimClick);
    document.addEventListener('keydown', this._onKeydown);
    this.addBtn.addEventListener('click', this._onAddClick);
    window.addEventListener('resize', this._onResize);
  }

  async init() {
    const { items } = await this.store.load();
    this.projects = items.map(d => new Project(d));
    this.relayoutAndRender();
  }

  destroy() {
    clearTimeout(this._resizeT);
    window.removeEventListener('resize', this._onResize);
    document.removeEventListener('keydown', this._onKeydown);
    this.dimEl.removeEventListener('click', this._onDimClick);
    this.addBtn.removeEventListener('click', this._onAddClick);
  }

  relayout() {
    const r = this.map.getBoundingClientRect();
    this.layout.run(this.projects, r.width, r.height);
  }

  render() {
    const seen = new Set();
    for (const p of this.projects) {
      seen.add(p.id);
      let b = this.bubbles.get(p.id);
      if (!b) {
        b = new ProjBubble(p, this);
        this.bubbles.set(p.id, b);
        this.map.appendChild(b.el);
      } else if (!b.expanded) {
        b.renderIdle();
      }
      b.place();
    }
    for (const [id, b] of this.bubbles) {
      if (!seen.has(id)) { b.el.remove(); this.bubbles.delete(id); }
    }
  }

  relayoutAndRender() { this.relayout(); this.render(); }

  setOpen(bubble) { this.openBubble = bubble; this.setDim(true); }

  collapseAll() {
    if (this.openBubble) { this.openBubble.collapse(); this.openBubble = null; }
    this.setDim(false);
  }

  setDim(on) {
    this.dimEl.classList.toggle('proj-on', on);
    this.addBtn.classList.toggle('proj-hidden', on);
  }

  async persist() { await this.store.save(this.projects.map(p => p.toJSON())); }

  add() {
    this.collapseAll();
    const p = new Project({ name: t('proj.defaultName'), priority: 'cyan' });
    this.projects.push(p);
    this.relayoutAndRender();
    this.persist();
    const b = this.bubbles.get(p.id);
    if (b) b.expand();
  }

  remove(id) {
    this.projects = this.projects.filter(p => p.id !== id);
    const b = this.bubbles.get(id);
    if (b) { b.el.remove(); this.bubbles.delete(id); }
    this.openBubble = null;
    this.setDim(false);
    this.relayoutAndRender();
    this.persist();
  }
}

/* ── React wrapper ── */
function ProjectsView() {
  const rootRef = projUseRef(null);
  const boardRef = projUseRef(null);

  useEffect(() => {
    const board = new ProjBoard(rootRef.current);
    boardRef.current = board;
    board.init();
    return () => board.destroy();
  }, []);

  return (
    <div className="proj-root" ref={rootRef}>
      <div className="proj-bg-orb proj-bg-orb-1"></div>
      <div className="proj-bg-orb proj-bg-orb-2"></div>
      <div className="proj-bg-orb proj-bg-orb-3"></div>
      <div id="proj-map">
        <div id="proj-dim"></div>
      </div>
      <button id="proj-add" title={t('proj.add')} onClick={() => boardRef.current?.add()}>+</button>
    </div>
  );
}

window.ProjectsView = ProjectsView;
