<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>{% block title %}{{ BRAND_NAME }}{% endblock %}</title>
  {# Cross-user contamination guard.

     localStorage is browser-wide; if User A uploads a portfolio and User B
     logs in on the same browser, the stale `cassandra.pie` would otherwise
     render as User B's holdings. We stamp the logged-in user's id in
     localStorage on every authenticated page load and wipe per-user keys
     if the id changed since last time. Theme stays — it's cosmetic. #}
  <script>
    (function() {
      try {
        var current = "{{ cu.user.id if cu and cu.user else '' }}";
        if (!current) return;
        var last = localStorage.getItem('cassandra.user_id') || '';
        if (last && last !== current) {
          var theme = localStorage.getItem('cassandra.theme');
          localStorage.clear();
          if (theme) localStorage.setItem('cassandra.theme', theme);
          try { sessionStorage.clear(); } catch (e) {}
        }
        localStorage.setItem('cassandra.user_id', current);
      } catch (e) {}
    })();
  </script>
  {# Apply saved theme before stylesheet renders to avoid a flash. #}
  <script>
    (function() {
      try {
        var t = localStorage.getItem('cassandra.theme') || 'light';
        document.documentElement.dataset.theme = t;
      } catch (e) { document.documentElement.dataset.theme = 'light'; }
    })();
  </script>
  <link rel="stylesheet" href="{{ url_for('static', path='/css/tokens.css') }}?v={{ ASSET_VERSION }}" />
  <link rel="stylesheet" href="{{ url_for('static', path='/css/layout.css') }}?v={{ ASSET_VERSION }}" />
  <link rel="stylesheet" href="{{ url_for('static', path='/css/panels.css') }}?v={{ ASSET_VERSION }}" />
  <link rel="stylesheet" href="{{ url_for('static', path='/css/dashboard.css') }}?v={{ ASSET_VERSION }}" />
  <link rel="stylesheet" href="{{ url_for('static', path='/css/portfolio.css') }}?v={{ ASSET_VERSION }}" />
  <link rel="stylesheet" href="{{ url_for('static', path='/css/log-chat.css') }}?v={{ ASSET_VERSION }}" />
  <link rel="stylesheet" href="{{ url_for('static', path='/css/auth.css') }}?v={{ ASSET_VERSION }}" />
  <link rel="stylesheet" href="{{ url_for('static', path='/css/settings.css') }}?v={{ ASSET_VERSION }}" />
  <link rel="stylesheet" href="{{ url_for('static', path='/css/news.css') }}?v={{ ASSET_VERSION }}" />
  <script src="{{ url_for('static', path='/js/htmx.min.js') }}?v={{ ASSET_VERSION }}" defer></script>
  <script>
    // Inject the user's preferred TONE (NOVICE | INTERMEDIATE) into every
    // HTMX request so AI-generated panels resolve to the right cached
    // variant. Preference persists in localStorage; see toggle in header.
    document.addEventListener('DOMContentLoaded', function () {
      function currentTone() {
        try {
          var t = localStorage.getItem('cassandra.tone');
          return (t === 'NOVICE' || t === 'INTERMEDIATE') ? t : 'INTERMEDIATE';
        } catch (e) { return 'INTERMEDIATE'; }
      }
      document.body.addEventListener('htmx:configRequest', function (evt) {
        evt.detail.parameters.tone = currentTone();
        // News tag filters — only attach to /api/news requests.
        if ((evt.detail.path || '').indexOf('/api/news') === 0) {
          var inc = newsTags('include');
          var exc = newsTags('exclude');
          if (inc.length) evt.detail.parameters.tags = inc.join(',');
          if (exc.length) evt.detail.parameters.exclude_tags = exc.join(',');
        }
      });

      // News tag preference: include / exclude sets persisted in
      // localStorage. Click cycles include → exclude → off;
      // shift-click goes straight to exclude.
      function newsTags(kind) {
        try {
          var raw = localStorage.getItem('cassandra.news.' + kind);
          var arr = raw ? JSON.parse(raw) : [];
          return Array.isArray(arr) ? arr : [];
        } catch (e) { return []; }
      }
      function setNewsTags(kind, arr) {
        try { localStorage.setItem('cassandra.news.' + kind, JSON.stringify(arr)); }
        catch (e) {}
      }
      function refreshNewsPanels() {
        document.querySelectorAll('[hx-get*="/api/news"]').forEach(function (el) {
          if (window.htmx) window.htmx.trigger(el, 'tags-changed');
        });
      }
      // Event delegation so HTMX-swapped pills work without rebinding.
      document.addEventListener('click', function (e) {
        var el = e.target.closest && e.target.closest('.news-tag');
        if (!el) return;
        e.preventDefault();
        var tag = el.getAttribute('data-tag') || '';
        if (el.classList.contains('news-tag--clear')) {
          setNewsTags('include', []);
          setNewsTags('exclude', []);
          refreshNewsPanels();
          return;
        }
        var inc = newsTags('include');
        var exc = newsTags('exclude');
        var inInc = inc.indexOf(tag);
        var inExc = exc.indexOf(tag);
        if (e.shiftKey) {
          // Shift-click → toggle exclude membership; remove from include.
          if (inInc >= 0) inc.splice(inInc, 1);
          if (inExc >= 0) exc.splice(inExc, 1);
          else exc.push(tag);
        } else {
          // Plain click → cycle: off → include → exclude → off.
          if (inInc >= 0) {
            inc.splice(inInc, 1);
            exc.push(tag);
          } else if (inExc >= 0) {
            exc.splice(inExc, 1);
          } else {
            inc.push(tag);
          }
        }
        setNewsTags('include', inc);
        setNewsTags('exclude', exc);
        refreshNewsPanels();
      });
      // Reflect the saved value in the toggle on load.
      var pill = document.getElementById('tone-toggle');
      if (pill) pill.dataset.tone = currentTone();
      // Same for the theme toggle — pull the current theme that the
      // top-of-page inline script already wrote to <html data-theme>.
      var themePill = document.getElementById('theme-toggle');
      if (themePill) themePill.dataset.theme = document.documentElement.dataset.theme || 'light';
      // Sync the /log page's tone badge to the saved tone — server-side
      // first render defaults to "pro", but a returning NOVICE user
      // should see "novice" before any toggle interaction.
      window.cassandraSyncToneBadge(currentTone());
    });

    // Sync the optional #tone-badge (currently used on the /log page) to
    // the supplied tone. NOVICE renders as "novice"; INTERMEDIATE renders
    // as "pro" — matches the header toggle's display labels. Safe to call
    // on pages that don't render the badge.
    window.cassandraSyncToneBadge = function (tone) {
      var badge = document.getElementById('tone-badge');
      if (!badge) return;
      var label = (tone === 'NOVICE') ? 'novice' : 'pro';
      badge.className = 'badge badge--tone-' + label;
      var span = badge.querySelector('[data-tone-label]');
      if (span) span.textContent = label;
    };

    window.cassandraSetTone = function (newTone) {
      try { localStorage.setItem('cassandra.tone', newTone); } catch (e) {}
      var pill = document.getElementById('tone-toggle');
      if (pill) pill.dataset.tone = newTone;
      window.cassandraSyncToneBadge(newTone);
      // Trigger a re-fetch of every AI-driven HTMX target on the page.
      // Easiest: dispatch a custom event that the relevant elements
      // listen to. Simpler still: fire htmx.trigger on the well-known
      // panels. We use the simple path.
      ['#dash-header-container', '#log-panel .panel-body',
       '#indicators-body', '#log-content'].forEach(function (sel) {
        var el = document.querySelector(sel);
        if (el && window.htmx) window.htmx.trigger(el, 'tone-changed');
      });
    };

    window.cassandraSetTheme = function (newTheme) {
      document.documentElement.dataset.theme = newTheme;
      var pill = document.getElementById('theme-toggle');
      if (pill) pill.dataset.theme = newTheme;
      try { localStorage.setItem('cassandra.theme', newTheme); } catch (e) {}
    };

    // Static-label i18n dictionary. AI-generated content is re-fetched via
    // HTMX (server-side translation), but plain UI labels are baked into
    // the HTML at render time. This dict + applyI18n() below let the
    // language toggle swap labels live without a page refresh.
    // Convention: data-i18n="key" sets textContent;
    //             data-i18n-placeholder="key" sets .placeholder.
    // First-render correctness is handled by the template's user_lang
    // conditional, so applyI18n only kicks in on subsequent toggles.
    window.CASSANDRA_I18N = {
      'chat.title':       { en: 'Ask Cassandra',
                            it: 'Chiedi a Cassandra' },
      'chat.hint':        { en: 'grounded on the latest log + live data',
                            it: "basato sull'ultimo log + dati in tempo reale" },
      'chat.lede':        { en: "Ask about today's analysis. The model sees the latest strategic log, live market readings across all groups, and the last 24h of thesis-filtered headlines. Refresh wipes this conversation.",
                            it: "Fai domande sull'analisi di oggi. Il modello vede l'ultimo log strategico, le quotazioni di mercato in tempo reale per tutti i gruppi e le ultime 24h di titoli filtrati per tesi. Un refresh della pagina cancella questa conversazione." },
      'chat.placeholder': { en: 'e.g. why is the defence sleeve flat through Hormuz?',
                            it: 'es. perché il comparto difesa è piatto nonostante Hormuz?' },
      'chat.send':        { en: 'Send',
                            it: 'Invia' },
    };
    window.cassandraApplyI18n = function (lang) {
      document.querySelectorAll('[data-i18n]').forEach(function (el) {
        var key = el.getAttribute('data-i18n');
        var entry = window.CASSANDRA_I18N[key];
        if (entry && entry[lang] != null) el.textContent = entry[lang];
      });
      document.querySelectorAll('[data-i18n-placeholder]').forEach(function (el) {
        var key = el.getAttribute('data-i18n-placeholder');
        var entry = window.CASSANDRA_I18N[key];
        if (entry && entry[lang] != null) el.placeholder = entry[lang];
      });
    };

    window.cassandraSetLang = async function (newLang) {
      var pill = document.getElementById('lang-toggle');
      if (!pill) return;
      var prev = pill.dataset.lang;
      if (prev === newLang) return;
      // Optimistic update — flip the pill immediately so the click feels
      // responsive. Revert on PATCH failure.
      pill.dataset.lang = newLang;
      try {
        var r = await fetch('/api/settings/language', {
          method: 'PATCH',
          headers: {'Content-Type': 'application/json'},
          credentials: 'same-origin',
          body: JSON.stringify({lang: newLang}),
        });
        if (!r.ok) throw new Error('HTTP ' + r.status);
        // Swap any static UI labels that have i18n bindings.
        window.cassandraApplyI18n(newLang);
        // Trigger HTMX-driven panels to re-fetch in the new language.
        // Same shape as cassandraSetTone — every panel that listens to
        // tone-changed also listens to lang-changed.
        ['#dash-header-container', '#log-panel .panel-body',
         '#indicators-body', '#log-content'].forEach(function (sel) {
          var el = document.querySelector(sel);
          if (el && window.htmx) window.htmx.trigger(el, 'lang-changed');
        });
      } catch (e) {
        pill.dataset.lang = prev;
        console.warn('language switch failed:', e);
      }
    };
  </script>
  <script>
    // Render any <time datetime="..."> in the browser's local timezone.
    // Re-runs after every HTMX swap so freshly-loaded news rows pick up too.
    function formatLocalTimes() {
      document.querySelectorAll('time[datetime]:not([data-local])').forEach(function (t) {
        try {
          var d = new Date(t.getAttribute('datetime'));
          if (isNaN(d.getTime())) return;
          var date = d.toLocaleDateString(undefined, { day: '2-digit', month: 'short' });
          var time = d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false });
          t.textContent = date + ' ' + time;
          t.title = d.toLocaleString();
          t.setAttribute('data-local', '1');
        } catch (e) {}
      });
    }
    document.addEventListener('DOMContentLoaded', function () {
      formatLocalTimes();
      document.body.addEventListener('htmx:afterSwap', formatLocalTimes);
    });
  </script>
</head>
<body>
<div class="app">
  <header class="app-header">
    {# Left group keeps brand + BETA chip pinned together as a single
       layout cell so the chip can't drift away from the wordmark when
       the header grows or shrinks. #}
    <div class="header-left">
      <a href="/" class="brand" aria-label="Dashboard">{{ BRAND_NAME }}</a>
      {% if BETA_MODE %}<span class="beta-chip" title="Beta — feedback welcome at hello@read.markets">BETA</span>{% endif %}
    </div>

    {# Mobile hamburger — shown only at ≤480px via CSS. #}
    <button type="button" id="drawer-toggle" class="drawer-toggle"
            aria-label="Open menu" aria-controls="mobile-drawer" aria-expanded="false">
      <span class="drawer-toggle__bar"></span>
      <span class="drawer-toggle__bar"></span>
      <span class="drawer-toggle__bar"></span>
    </button>

    {# Wrapper: display:contents on desktop (zero layout effect), fixed
       slide-out panel on mobile. Holds nav + header-right widgets. #}
    <div id="mobile-drawer" class="mobile-drawer">
      <nav>
        <a href="/"     class="{% if request.url.path == '/' %}active{% endif %}">Dashboard</a>
        <a href="/news" class="{% if request.url.path == '/news' %}active{% endif %}">News</a>
        <a href="/log"  class="{% if request.url.path.startswith('/log') %}active{% endif %}">Log</a>
      </nav>
      <div class="header-right">
        {# The "Pro" label maps to the INTERMEDIATE tone server-side —
           kept that way to avoid touching every stored user preference
           and API contract. The mode itself (terse, no glossary
           tooltips, assumes fluency) is unchanged; only the display
           label changes. #}
        <div id="tone-toggle" class="tone-toggle" data-tone="INTERMEDIATE"
             role="group" aria-label="Explanation level">
          <button type="button" data-value="NOVICE"
                  onclick="cassandraSetTone('NOVICE')">Novice</button>
          <button type="button" data-value="INTERMEDIATE"
                  onclick="cassandraSetTone('INTERMEDIATE')">Pro</button>
        </div>
        <div id="theme-toggle" class="theme-toggle" data-theme="light"
             role="group" aria-label="Theme">
          <button type="button" data-value="light"
                  onclick="cassandraSetTheme('light')">Light</button>
          <button type="button" data-value="dark"
                  onclick="cassandraSetTheme('dark')">Dark</button>
        </div>
        {% set cu = request.state.current_user if request.state and request.state.current_user is defined else None %}
        {% if cu and cu.user %}
        <div id="lang-toggle" class="lang-toggle" data-lang="{{ cu.user.lang or 'en' }}"
             role="group" aria-label="AI output language"
             title="Language the AI uses for the log, digest and portfolio commentary">
          <button type="button" data-value="en"
                  onclick="cassandraSetLang('en')">EN</button>
          <button type="button" data-value="it"
                  onclick="cassandraSetLang('it')">IT</button>
        </div>
        {% endif %}
        {% if cu and (cu.user or cu.is_admin) %}
        <div class="user-menu">
          <button type="button" id="user-menu-toggle" class="user-chip"
                  aria-haspopup="true" aria-expanded="false">
            {% if cu.user %}{{ cu.user.email }}{% else %}admin{% endif %}
            <span class="user-menu__caret">▾</span>
          </button>
          <div id="user-menu" class="user-menu__panel" role="menu" hidden>
            {% if cu.user %}
            <a href="/settings" role="menuitem" class="user-menu__item">Settings</a>
            {% endif %}
            {% if SUBSCRIPTIONS_ENABLED %}
            <a href="/pricing"    role="menuitem" class="user-menu__item">Pricing</a>
            {% endif %}
            <a href="/terms"      role="menuitem" class="user-menu__item">Terms</a>
            <a href="/privacy"    role="menuitem" class="user-menu__item">Privacy</a>
            <a href="/disclaimer" role="menuitem" class="user-menu__item">Disclaimer</a>
            <a href="/logout"     role="menuitem" class="user-menu__item">Logout</a>
          </div>
        </div>
        {% endif %}
        <span class="meta">v0.1 · UTC</span>
      </div>
    </div>
  </header>

  {# Drawer backdrop. Hidden by default; CSS shows it when
     body.drawer-open is set. Click closes the drawer. #}
  <div id="drawer-backdrop" class="drawer-backdrop" hidden></div>

  <script>
    (function () {
      var btn = document.getElementById('user-menu-toggle');
      var menu = document.getElementById('user-menu');
      if (!btn || !menu) return;
      function close() { menu.hidden = true; btn.setAttribute('aria-expanded','false'); }
      function open()  { menu.hidden = false; btn.setAttribute('aria-expanded','true'); }
      btn.addEventListener('click', function (e) {
        e.stopPropagation();
        if (menu.hidden) open(); else close();
      });
      document.addEventListener('click', function (e) {
        if (!menu.hidden && !menu.contains(e.target) && e.target !== btn) close();
      });
      document.addEventListener('keydown', function (e) {
        if (e.key === 'Escape') close();
      });
    })();
  </script>

  <script>
    // Mobile drawer (hamburger → right-side slide-out panel). The CSS
    // gates visibility of #drawer-toggle to ≤480px, so on desktop this
    // wiring is harmless — the click handler is attached but nobody
    // can fire it.
    (function () {
      var btn = document.getElementById('drawer-toggle');
      var drawer = document.getElementById('mobile-drawer');
      var backdrop = document.getElementById('drawer-backdrop');
      if (!btn || !drawer || !backdrop) return;
      function open() {
        document.body.classList.add('drawer-open');
        backdrop.hidden = false;
        btn.setAttribute('aria-expanded', 'true');
      }
      function close() {
        document.body.classList.remove('drawer-open');
        backdrop.hidden = true;
        btn.setAttribute('aria-expanded', 'false');
      }
      btn.addEventListener('click', function () {
        if (document.body.classList.contains('drawer-open')) close(); else open();
      });
      backdrop.addEventListener('click', close);
      document.addEventListener('keydown', function (e) {
        if (e.key === 'Escape' && document.body.classList.contains('drawer-open')) close();
      });
      // Swipe-right inside the drawer closes — small native feel.
      // Tracks pointer down then up; if the user moves >40px right
      // and stays within 60° of horizontal, treat as a close gesture.
      var startX = null, startY = null;
      drawer.addEventListener('pointerdown', function (e) {
        if (!document.body.classList.contains('drawer-open')) return;
        startX = e.clientX; startY = e.clientY;
      });
      drawer.addEventListener('pointerup', function (e) {
        if (startX === null) return;
        var dx = e.clientX - startX;
        var dy = Math.abs(e.clientY - startY);
        startX = startY = null;
        if (dx > 40 && dy < dx * 0.6) close();
      });
      // If a nav link inside the drawer is clicked, close after the
      // navigation kicks off so the panel doesn't linger on the next page.
      drawer.addEventListener('click', function (e) {
        if (e.target.tagName === 'A') close();
      });
    })();
  </script>

  <main class="app-main">
    {% block main %}{% endblock %}
  </main>

  {# Shared glossary tooltip (Novice mode). Single floating element
     positioned by JS to escape sticky-bar stacking and viewport edges. #}
  <div id="glossary-tooltip" role="tooltip" hidden></div>
  <script>
    (function () {
      const tip = document.getElementById('glossary-tooltip');
      let activeEl = null;

      function position(el) {
        // Measure after content is set so dimensions are accurate.
        tip.style.left = '0px';
        tip.style.top = '0px';
        tip.hidden = false;
        const rect = el.getBoundingClientRect();
        const tipRect = tip.getBoundingClientRect();
        const margin = 8;

        // Decide above or below based on available space.
        const spaceAbove = rect.top - margin;
        const spaceBelow = window.innerHeight - rect.bottom - margin;
        let top = (spaceAbove >= tipRect.height || spaceAbove >= spaceBelow)
          ? rect.top - tipRect.height - 6
          : rect.bottom + 6;

        // Clamp top into the viewport.
        if (top < margin) top = margin;
        if (top + tipRect.height > window.innerHeight - margin) {
          top = window.innerHeight - tipRect.height - margin;
        }

        // Horizontal: anchor to term's left edge, clamp to viewport.
        let left = rect.left;
        if (left + tipRect.width > window.innerWidth - margin) {
          left = window.innerWidth - tipRect.width - margin;
        }
        if (left < margin) left = margin;

        tip.style.left = left + 'px';
        tip.style.top = top + 'px';
        tip.setAttribute('data-visible', '1');
      }

      function show(el) {
        const def = el.getAttribute('data-def');
        if (!def) return;
        activeEl = el;
        tip.textContent = def;
        position(el);
      }

      function hide() {
        activeEl = null;
        tip.removeAttribute('data-visible');
        tip.hidden = true;
      }

      // Event delegation with capture so we catch elements HTMX swaps in
      // after page load.
      document.addEventListener('mouseover', function (e) {
        const el = e.target.closest && e.target.closest('.glossary');
        if (el) show(el);
        else if (activeEl && !e.target.closest('.glossary')) hide();
      }, true);
      document.addEventListener('focusin', function (e) {
        if (e.target.classList && e.target.classList.contains('glossary')) {
          show(e.target);
        }
      });
      document.addEventListener('focusout', function (e) {
        if (e.target.classList && e.target.classList.contains('glossary')) {
          hide();
        }
      });

      // Mobile / touch: tap to toggle, tap-elsewhere to dismiss.
      document.addEventListener('click', function (e) {
        const el = e.target.closest && e.target.closest('.glossary');
        if (el) {
          // Re-show (or toggle off if it's the currently-active one).
          if (activeEl === el) hide();
          else show(el);
          e.preventDefault();
        } else if (activeEl) {
          hide();
        }
      }, true);

      // Hide on scroll / resize so the tooltip doesn't drift away from
      // its term.
      window.addEventListener('scroll', hide, true);
      window.addEventListener('resize', hide);
      // Hide when HTMX swaps content (term may have been replaced).
      document.body.addEventListener('htmx:beforeSwap', hide);
    })();
  </script>

  <footer class="markets-bar"
          hx-get="/api/markets-bar?as=html"
          hx-trigger="load, every 60s"
          hx-swap="innerHTML"
          id="markets-bar">
    <div class="markets-bar__inner">
      <div class="markets-bar__list"><span class="empty">awaiting markets…</span></div>
    </div>
  </footer>
</div>
</body>
</html>
