import * as NotesApi from './notes/api.js';
import { createNotesEditor } from './notes/editor.js';
import { renderNoteList, updateTrashCount as renderTrashCount } from './notes/list.js';
import { createNotesRecorder } from './notes/recorder.js';
import { esc, formatWhen, sanitizeHtml } from './notes/utils.js';

// ============================================================
// NOTES — per-user personal scratchpad under Clinical Tools.
// Rich-text via the app-wide Tiptap bundle (window.Tiptap).
// CRUD against /api/notes (auth + server-side encryption).
//
// Three panes — list / reader / editor. After opening or saving
// a note, the reader shows a clean, read-only rendering; tap
// Edit to switch to the Tiptap editor. Autosaves during edit
// (1.2s quiet period) so you never lose progress if you close
// the tab or the browser crashes.
//
// On mobile (<900px) only one pane is visible at a time — the
// layout's data-view attribute controls which one. Desktop
// always shows the sidebar + the right pane together.
// ============================================================
  var _inited = false;
  var _notes = [];             // active notes (deleted_at IS NULL)
  var _trash = [];             // trashed notes (deleted_at IS NOT NULL)
  var _pane = 'active';        // 'active' | 'trash' — drives the list view
  var _activeId = null;        // currently open note id (reader OR editor)
  var _search = '';

  // Autosave state
  var _dirty = false;
  var _autosaveTimer = null;
  var _autosaveInFlight = false;
  var _pendingAfterInflight = false;
  var AUTOSAVE_DEBOUNCE = 1200;

  var _statusTimer = null;
  var _editor = createNotesEditor({
    onDirty: function() { _dirty = true; },
    onStatus: updateStatus,
    onAutosave: scheduleAutosave
  });
  var _recorder = createNotesRecorder({
    getEl: $,
    updateStatus: updateStatus,
    ensureEditor: function() { if ($('notes-editor').classList.contains('hidden')) startCreate(); },
    noteFromVoice: NotesApi.noteFromVoice,
    applyGeneratedNote: applyGeneratedNote,
    markDirty: function() { _dirty = true; },
    scheduleAutosave: scheduleAutosave
  });

  // Wait for the Notes tab to be activated — the component HTML is
  // lazy-loaded, so elements don't exist until the user clicks the tab.
  document.addEventListener('tabChanged', function(e) {
    if (e.detail.tab === 'notes') {
      if (_inited) { refreshList(); return; }
      _inited = true;
      init();
      return;
    }
    // Leaving the Notes tab — cancel any in-flight recording so the
    // mic doesn't stay live + the recorder doesn't keep buffering.
    if (_recorder.isActive()) _recorder.stop(true);
    flushAutosave();
  });

  function $(id) { return document.getElementById(id); }

  function init() {
    var newBtn      = $('btn-notes-new');
    var searchEl    = $('notes-search');
    var titleEl     = $('note-title');
    var listEl      = $('notes-list');
    if (!newBtn || !listEl) return;

    // List + sidebar
    newBtn.addEventListener('click', function() { startCreate(); });
    searchEl.addEventListener('input', function() {
      _search = searchEl.value.trim().toLowerCase();
      renderList();
    });
    listEl.addEventListener('click', function(e) {
      // Trash-view actions get handled by their own classes; route them
      // first so a click on Restore or × doesn't also open the note.
      var restoreBtn = e.target.closest('.notes-restore-btn');
      if (restoreBtn) {
        e.stopPropagation();
        restoreNote(parseInt(restoreBtn.dataset.id));
        return;
      }
      var hardBtn = e.target.closest('.notes-hard-delete-btn');
      if (hardBtn) {
        e.stopPropagation();
        hardDeleteNote(parseInt(hardBtn.dataset.id));
        return;
      }
      var item = e.target.closest('.notes-list-item');
      if (!item) return;
      var id = parseInt(item.dataset.id);
      if (!id) return;
      if (_pane === 'active') openReader(id);
      // In trash, clicking the row body is a no-op — explicit Restore /
      // delete-forever buttons are the only actions, so a stray click
      // can't accidentally open a stale draft.
    });

    // Active <-> Trash tab toggle
    var tabActive = $('btn-notes-tab-active');
    var tabTrash  = $('btn-notes-tab-trash');
    if (tabActive) tabActive.addEventListener('click', function() { switchPane('active'); });
    if (tabTrash)  tabTrash.addEventListener('click',  function() { switchPane('trash'); });

    var emptyTrashBtn = $('btn-notes-trash-empty');
    if (emptyTrashBtn) emptyTrashBtn.addEventListener('click', emptyTrash);

    // Empty-state CTA
    var newFromEmpty = $('btn-notes-new-empty');
    if (newFromEmpty) newFromEmpty.addEventListener('click', function() { startCreate(); });

    // Reader pane
    var editBtn = $('btn-note-edit');
    var delFromRead = $('btn-note-delete-read');
    var backFromRead = $('btn-notes-reader-back');
    if (editBtn)       editBtn.addEventListener('click', function() { if (_activeId != null) openEditor(_activeId); });
    if (delFromRead)   delFromRead.addEventListener('click', deleteActive);
    if (backFromRead)  backFromRead.addEventListener('click', closeToList);

    // Editor pane
    var saveBtn     = $('btn-note-save');
    var delBtn      = $('btn-note-delete');
    var backFromEdit = $('btn-notes-editor-back');
    if (saveBtn)       saveBtn.addEventListener('click', function() { saveNote({ source: 'manual', returnToReader: true }); });
    if (delBtn)        delBtn.addEventListener('click', deleteActive);
    if (backFromEdit)  backFromEdit.addEventListener('click', backFromEditor);

    titleEl.addEventListener('input', function() {
      _dirty = true; updateStatus('Saving…', 'saving'); scheduleAutosave();
    });

    // Voice recording controls. addEventListener passes the click Event
    // as the first argument — wrap so the truthy Event doesn't get passed
    // to stopRecording(silent) and trigger the silent-cancel branch
    // (which shuts the mic without transcribing → "Stop does nothing").
    var recStart = $('btn-note-rec-start');
    var recPause = $('btn-note-rec-pause');
    var recStop  = $('btn-note-rec-stop');
    if (recStart) recStart.addEventListener('click', function() { _recorder.start(); });
    if (recPause) recPause.addEventListener('click', function() { _recorder.togglePause(); });
    if (recStop)  recStop.addEventListener('click',  function() { _recorder.stop(false); });

    // Ctrl/Cmd+S still saves manually from the editor
    document.addEventListener('keydown', function(e) {
      var tab = $('notes-tab');
      if (!tab || !tab.classList.contains('active')) return;
      if ((e.ctrlKey || e.metaKey) && e.key === 's') {
        if (!$('notes-editor').classList.contains('hidden')) {
          e.preventDefault();
          saveNote({ source: 'shortcut', returnToReader: true });
        }
      }
    });

    // Persist dirty state on tab close / page unload — best-effort final flush.
    // navigator.sendBeacon is POST-only so it can't drive a PUT update; fetch
    // with keepalive:true is the right primitive here (works for both POST
    // for new notes and PUT for existing).
    window.addEventListener('beforeunload', function() {
      if (!_dirty) return;
      var title = getTitle();
      if (!title) return;
      var body = getBody();
      var isNew = _activeId == null;
      try {
        NotesApi.saveNoteKeepalive(isNew ? null : _activeId, { title: title, body: body });
      } catch (e) {}
    });

    refreshList();
    setView('list');
  }

  // ── View management ──────────────────────────────────────
  // On mobile (<900px), only one pane visible at a time, driven
  // by the layout's data-view attribute.
  function setView(view) {
    var layout = $('notes-layout');
    if (layout) layout.setAttribute('data-view', view);

    var reader = $('notes-reader');
    var editor = $('notes-editor');
    var empty  = $('notes-empty-state');

    if (reader) reader.classList.toggle('hidden', view !== 'reader');
    if (editor) editor.classList.toggle('hidden', view !== 'editor');
    // Empty state is only the "no note picked" desktop placeholder
    if (empty)  empty.classList.toggle('hidden', view !== 'empty' && view !== 'list');
  }

  function closeToList() {
    // Any unsaved edits are already autosaved, so no discard prompt needed.
    flushAutosave();
    _activeId = null;
    _dirty = false;
    _editor.destroy();
    _recorder.stop(true); // silent cancel if in flight
    setView('list');
    renderList();
  }

  function backFromEditor() {
    flushAutosave();
    if (_activeId != null) openReader(_activeId);
    else closeToList();
  }

  // ── Data fetching ─────────────────────────────────────────
  // Always pulls both lists in parallel so the trash-count badge stays
  // current regardless of which pane the user has open.
  function refreshList() {
    var listEl = $('notes-list');
    if (!listEl) return;
    listEl.innerHTML = '<div class="notes-empty">Loading…</div>';
    Promise.all([
      NotesApi.listActive(),
      NotesApi.listTrash(),
    ])
      .then(function(results) {
        var active = results[0], trash = results[1];
        if (active && active.success) _notes = active.notes || [];
        if (trash  && trash.success)  _trash = trash.notes  || [];
        renderList();
        updateTrashCount();
      })
      .catch(function(err) {
        listEl.innerHTML = '<div class="notes-empty">' + esc(err.message || 'Load failed') + '</div>';
      });
  }

  function switchPane(pane) {
    if (pane === _pane) return;
    _pane = pane;
    var tabA = $('btn-notes-tab-active');
    var tabT = $('btn-notes-tab-trash');
    if (tabA) tabA.classList.toggle('active', pane === 'active');
    if (tabT) tabT.classList.toggle('active', pane === 'trash');
    var foot = $('notes-trash-foot');
    if (foot) foot.classList.toggle('hidden', pane !== 'trash');
    // Switching to trash clears any open editor — trashed notes are
    // read-only previews, opening them is intentionally disabled.
    if (pane === 'trash') closeToList();
    renderList();
  }

  function updateTrashCount() {
    renderTrashCount($('notes-trash-count'), _trash.length);
  }

  function renderList() {
    renderNoteList($('notes-list'), {
      pane: _pane,
      notes: _notes,
      trash: _trash,
      search: _search,
      activeId: _activeId
    });
  }

  function restoreNote(id) {
    NotesApi.restore(id)
      .then(function(data) {
        if (!data.success) { if (typeof showToast === 'function') showToast(data.error || 'Restore failed', 'error'); return; }
        if (typeof showToast === 'function') showToast('Note restored', 'success');
        refreshList();
      })
      .catch(function(err) {
        if (typeof showToast === 'function') showToast(err.message || 'Restore failed', 'error');
      });
  }

  function hardDeleteNote(id) {
    if (typeof showConfirm !== 'function') return;
    showConfirm('Delete forever? This cannot be undone.', function() {
      NotesApi.hardDelete(id)
        .then(function(data) {
          if (!data.success) { if (typeof showToast === 'function') showToast(data.error || 'Delete failed', 'error'); return; }
          refreshList();
        });
    }, { danger: true, confirmText: 'Delete forever' });
  }

  function emptyTrash() {
    if (typeof showConfirm !== 'function') return;
    if (_trash.length === 0) return;
    showConfirm('Empty the trash? This will permanently delete ' + _trash.length + ' note' + (_trash.length === 1 ? '' : 's') + '.',
      function() {
        NotesApi.emptyTrash()
          .then(function(data) {
            if (!data.success) { if (typeof showToast === 'function') showToast(data.error || 'Empty trash failed', 'error'); return; }
            if (typeof showToast === 'function') showToast('Trash emptied', 'success');
            refreshList();
          });
      },
      { danger: true, confirmText: 'Empty trash' });
  }

  // ── Reader mode (default after opening or saving) ─────────
  function openReader(id) {
    flushAutosave();
    if (_recorder.isActive()) _recorder.stop(true);
    var note = _notes.find(function(n) { return n.id === id; });
    if (!note) return;
    _activeId = id;
    _dirty = false;

    // Tear down the editor if it was open
    _editor.destroy();

    $('notes-reader-title').textContent = note.title || 'Untitled';
    $('notes-reader-meta').textContent  = 'Created ' + formatWhen(note.created_at) + ' · Updated ' + formatWhen(note.updated_at);

    var body = $('notes-reader-body');
    body.innerHTML = note.body ? sanitizeHtml(note.body) : '';

    setView('reader');
    renderList();
  }

  // ── Editor mode (create new / edit existing) ──────────────
  function startCreate() {
    flushAutosave();
    if (_recorder.isActive()) _recorder.stop(true);
    _activeId = null;
    _dirty = false;
    $('note-title').value = '';
    $('note-meta').textContent = '';
    $('btn-note-delete').style.display = 'none';
    _editor.mount($('note-body-editor'), '');
    updateStatus('', '');
    _recorder.setUI('idle');
    setView('editor');
    setTimeout(function() { $('note-title').focus(); }, 20);
    renderList();
  }

  function openEditor(id) {
    var note = _notes.find(function(n) { return n.id === id; });
    if (!note) return;
    _activeId = id;
    _dirty = false;

    $('note-title').value = note.title || '';
    $('note-meta').textContent = 'Created ' + formatWhen(note.created_at) + ' · Updated ' + formatWhen(note.updated_at);
    $('btn-note-delete').style.display = '';
    _editor.mount($('note-body-editor'), note.body || '');
    updateStatus('', '');
    _recorder.setUI('idle');
    setView('editor');
    renderList();
  }

  function getTitle() { return ($('note-title').value || '').trim(); }
  function getBody()  {
    return _editor.getBody();
  }

  // ── Save + autosave ──────────────────────────────────────
  function scheduleAutosave() {
    if (_autosaveTimer) clearTimeout(_autosaveTimer);
    _autosaveTimer = setTimeout(function() {
      _autosaveTimer = null;
      saveNote({ source: 'auto' });
    }, AUTOSAVE_DEBOUNCE);
  }

  function flushAutosave() {
    if (_autosaveTimer) { clearTimeout(_autosaveTimer); _autosaveTimer = null; }
    // Flush dirty state regardless of whether the note is new or existing.
    // saveNote() handles both paths (POST for new, PUT for existing), so
    // gating on _activeId here silently dropped the very first flush for
    // a brand-new note — e.g. tap New note → type → tap Back.
    if (_dirty) saveNote({ source: 'flush' });
  }

  function saveNote(opts) {
    opts = opts || {};
    var title = getTitle();
    var body  = getBody();

    // Title is required — but for autosave, don't nag every keystroke.
    if (!title) {
      if (opts.source === 'manual' || opts.source === 'shortcut') {
        updateStatus('Title required', 'err');
        $('note-title').focus();
      }
      return;
    }

    if (_autosaveInFlight) {
      // Another save is already pending the network — mark so we
      // fire one more right after it completes to capture the newest
      // state. Prevents "the last keystroke didn't save" races.
      _pendingAfterInflight = true;
      return;
    }

    var isNew = _activeId == null;

    _autosaveInFlight = true;
    updateStatus('Saving…', 'saving');

    NotesApi.saveNote(isNew ? null : _activeId, { title: title, body: body })
      .then(function(data) {
        _autosaveInFlight = false;
        if (!data.success) { updateStatus(data.error || 'Save failed', 'err'); return; }
        if (isNew && data.id) _activeId = data.id;
        _dirty = false;
        updateStatus('Saved', 'ok');

        // Refresh the list so the new/updated note appears with correct
        // timestamps + ordering.
        return NotesApi.listActive()
          .then(function(d) {
            if (d.success) _notes = d.notes || [];
            var note = _notes.find(function(n) { return n.id === _activeId; });
            if (note) {
              $('note-meta').textContent = 'Created ' + formatWhen(note.created_at) + ' · Updated ' + formatWhen(note.updated_at);
            }
            renderList();
          })
          .then(function() {
            // If the user typed more while the save was in flight, fire once more.
            if (_pendingAfterInflight) {
              _pendingAfterInflight = false;
              _dirty = true;
              scheduleAutosave();
            } else if (opts.returnToReader) {
              // Manual save → show the reader view with the saved content.
              openReader(_activeId);
            }
          });
      })
      .catch(function(err) {
        _autosaveInFlight = false;
        updateStatus(err.message || 'Save failed', 'err');
      });
  }

  // ── Delete ──────────────────────────────────────────────
  function deleteActive() {
    if (_activeId == null) return;
    var note = _notes.find(function(n) { return n.id === _activeId; });
    var label = (note && note.title) ? note.title : 'this note';

    // Soft-delete: row gets deleted_at timestamp and moves to Trash.
    // The user can Restore from there or Empty trash to actually erase.
    var doMove = function() {
      flushAutosave();
      NotesApi.moveToTrash(_activeId)
        .then(function(data) {
          if (!data.success) { updateStatus(data.error || 'Move to trash failed', 'err'); return; }
          _notes = _notes.filter(function(n) { return n.id !== _activeId; });
          _activeId = null;
          _dirty = false;
          _editor.destroy();
          closeToList();
          if (typeof showToast === 'function') showToast('Moved to trash', 'success');
          refreshList();
        })
        .catch(function(err) { updateStatus(err.message || 'Move to trash failed', 'err'); });
    };

    if (typeof showConfirm === 'function') {
      showConfirm('Move "' + label + '" to trash?', doMove, { confirmText: 'Move to trash' });
    }
  }

  function applyGeneratedNote(title, body) {
    var titleEl = $('note-title');
    var container = $('note-body-editor');
    if (!titleEl || !container) {
      console.error('[notes] applyGeneratedNote: editor not mounted; title/container missing');
      return;
    }
    titleEl.value = title || 'Voice note';
    _editor.applyGenerated(container, body);
  }

  // ── Helpers ──────────────────────────────────────────────
  function updateStatus(text, kind) {
    var el = $('notes-status');
    if (!el) return;
    el.textContent = text || '';
    el.className = 'notes-status' + (kind ? ' notes-status-' + kind : '');
    if (_statusTimer) { clearTimeout(_statusTimer); _statusTimer = null; }
    if (kind === 'ok') {
      _statusTimer = setTimeout(function() { el.textContent = ''; el.className = 'notes-status'; }, 2000);
    }
  }
