// app.jsx — root App component, i18n globals, pathname routing, lang/theme state.
// Loaded via <script type="text/babel" data-presets="react"> after components.jsx.

// --- i18n globals -------------------------------------------------
// Set before App renders so all components can read window.YUI_I18N[lang].key.

var I18N = {
  en: {
    writing:                  'Writing',
    about:                    'About',
    footer_text:              'yuisays.dev — Yui, director.',
    empty_posts:              'No dispatches yet.',
    comment_submit:           'Send',
    comment_name_placeholder: 'Your name',
    comment_url_placeholder:  'Your site (optional)',
    comment_body_placeholder: 'What do you think?',
    comment_success:          'Comment submitted. It will appear after review.',
    comment_error:            'Failed to submit. Try again.',
    back_to_writing:          '← Writing',
    post_not_found:           'Post not found.',
    min_read:                 'min read',
    comments_heading:         'Comments',
    comments_loading:         'Loading comments…',
  },
  es: {
    writing:                  'Escritura',
    about:                    'Acerca',
    footer_text:              'yuisays.dev — Yui, directora.',
    empty_posts:              'Todavía no hay despachos.',
    comment_submit:           'Enviar',
    comment_name_placeholder: 'Tu nombre',
    comment_url_placeholder:  'Tu sitio (opcional)',
    comment_body_placeholder: '¿Qué opinas?',
    comment_success:          'Comentario enviado. Aparecerá tras revisión.',
    comment_error:            'Error al enviar. Inténtalo de nuevo.',
    back_to_writing:          '← Escritura',
    post_not_found:           'Post no encontrado.',
    min_read:                 'min de lectura',
    comments_heading:         'Comentarios',
    comments_loading:         'Cargando comentarios…',
  },
};

window.YUI_I18N = I18N;
window.YUI_LANG = localStorage.getItem('yui-lang') || 'en';

// --- Helpers ------------------------------------------------------

// SAFE_SEGMENT: same predicate as routing-utils.js isSafeSegment.
// Inlined here because app.jsx cannot import ESM modules (it is loaded via
// @babel/standalone, not as a native <script type="module">).
var SAFE_SEGMENT = /^[a-zA-Z0-9_-]+$/;

// TAG predicate — same as routing-utils.js isValidTag.
var VALID_TAG = /^[a-z0-9-]+$/i;

// Expose on window so components.jsx (loaded before app.jsx) can share the
// same compiled regex objects without duplicating the pattern.
window.SAFE_SEGMENT_RE = SAFE_SEGMENT;
window.VALID_TAG_RE    = VALID_TAG;

/**
 * Navigate to a new path using pushState.
 * pushState does NOT fire popstate, so we dispatch a popstate event manually
 * to trigger the router re-render. This is the canonical pattern.
 *
 * Defense-in-depth: validate slug/tag segments before pushing to history so
 * that malformed paths cannot enter the history stack regardless of call site.
 * Invalid paths fall back to home ('/') rather than pushing a corrupt URL.
 *
 * @param {string} path — absolute pathname (e.g. '/p/my-slug')
 */
function navigate(path) {
  // SECURITY: validate the segment before pushState (defense-in-depth).
  // Guards against future call sites that may source paths dynamically.
  if (path.startsWith('/p/')) {
    var navSlug = path.slice(3);
    if (!SAFE_SEGMENT.test(navSlug)) {
      // Invalid slug — navigate home rather than push a malformed URL.
      path = '/';
    }
  } else if (path.startsWith('/tag/')) {
    var navTag = path.slice(5);
    if (!VALID_TAG.test(navTag)) {
      // Invalid tag — navigate home.
      path = '/';
    }
  }
  window.history.pushState(null, '', path);
  // Dispatch popstate so the router picks up the change.
  window.dispatchEvent(new PopStateEvent('popstate', { state: null }));
}

/**
 * Parse the current location into a route descriptor.
 * Reads window.location.pathname (and optionally hash for legacy compat).
 *
 * Returns one of:
 *   { view: 'about' }
 *   { view: 'post', slug: string }
 *   { view: 'home', activeTag: string|null }
 *   { view: 'not_found' }   — not used directly; post-not-found is handled via view:'post', post:undefined
 *
 * Priority order per RFC 020 §2:
 *   1. pathname === '/about'
 *   2. pathname === '/p/<slug>'     (slug validated; unknown slug still yields view:'post')
 *   3. pathname === '/tag/<tag>'    (tag validated)
 *   4. pathname === '/posts/<slug>' (client-side defense-in-depth; worker 301 handles most cases)
 *   5. hash  === '#/p/<slug>'       (legacy hash compat)
 *   6. hash  === '#/about'          (legacy hash compat)
 *   7. hash.startsWith('#/?') etc.  (legacy tag hash compat)
 *   8. pathname === '/'             → home
 *   9. fallback                     → home
 */
function parseRoute() {
  // Strip a single trailing slash before matching, except when pathname === '/'.
  var raw = window.location.pathname;
  var pathname = (raw !== '/' && raw.endsWith('/')) ? raw.slice(0, -1) : raw;
  var hash = window.location.hash || '';

  // 1. /about
  if (pathname === '/about') {
    return { view: 'about' };
  }

  // 2. /p/<slug> — canonical post route
  if (pathname.startsWith('/p/')) {
    var slug = pathname.slice(3);
    // Validate slug format (safe chars). Unknown slug falls through to post
    // view with undefined post → renders "Post not found" (fixed i18n string).
    if (SAFE_SEGMENT.test(slug)) {
      return { view: 'post', slug: slug };
    }
    // Invalid slug (unsafe chars) — fall through to home.
  }

  // 3. /tag/<tag> — filtered listing
  if (pathname.startsWith('/tag/')) {
    var tag = pathname.slice(5);
    if (VALID_TAG.test(tag)) {
      return { view: 'home', activeTag: tag };
    }
    // Invalid tag — fall through to home.
  }

  // 4. /posts/<slug> — legacy prose-link (client-side defense; worker 301 handles server-side)
  if (pathname.startsWith('/posts/')) {
    var legacySlug = pathname.slice(7).replace(/\/$/, '');
    if (SAFE_SEGMENT.test(legacySlug) && legacySlug.length > 0) {
      // Redirect to canonical path form without adding a history entry.
      window.history.replaceState(null, '', '/p/' + legacySlug);
      return { view: 'post', slug: legacySlug };
    }
  }

  // 5. Legacy hash: #/p/<slug>
  if (hash.startsWith('#/p/')) {
    var hashSlug = hash.slice(4).split('?')[0].split('#')[0];
    if (SAFE_SEGMENT.test(hashSlug)) {
      // The synchronous redirect IIFE in index.html handles this before React
      // mounts. If we somehow reach here, do a client-side redirect.
      window.location.replace('/p/' + hashSlug);
      return { view: 'post', slug: hashSlug };
    }
  }

  // 6. Legacy hash: #/about
  if (hash === '#/about') {
    window.location.replace('/about');
    return { view: 'about' };
  }

  // 7. Legacy hash: #/?tag=<value> or #?tag=<value>
  if (hash.startsWith('#/?') || hash.startsWith('#?')) {
    var queryStr = hash.indexOf('?') !== -1 ? hash.slice(hash.indexOf('?')) : '';
    try {
      var params = new URLSearchParams(queryStr);
      var hashTag = params.get('tag');
      if (hashTag && VALID_TAG.test(hashTag)) {
        window.location.replace('/tag/' + hashTag);
        return { view: 'home', activeTag: hashTag };
      }
    } catch (e) { /* ignore */ }
  }

  // 8. Root
  if (pathname === '/') {
    return { view: 'home', activeTag: null };
  }

  // 9. Fallback — show home
  return { view: 'home', activeTag: null };
}

// --- App ----------------------------------------------------------

function App() {
  var useState  = React.useState;
  var useEffect = React.useEffect;

  var langState = useState(window.YUI_LANG);
  var lang      = langState[0]; // eslint-disable-line no-unused-vars — used to trigger re-render
  var setLang   = langState[1];

  // Route state — initialized from current location.
  var routeState = useState(function() { return parseRoute(); });
  var route      = routeState[0];
  var setRoute   = routeState[1];

  // Listen for popstate (back/forward) and programmatic navigate() calls.
  // Also listen for lang changes to re-render children reading window.YUI_LANG.
  useEffect(function() {
    function onPopState() {
      setRoute(parseRoute());
    }
    function onLangChange() {
      setLang(window.YUI_LANG);
    }
    window.addEventListener('popstate', onPopState);
    document.addEventListener('langchange', onLangChange);
    return function() {
      window.removeEventListener('popstate', onPopState);
      document.removeEventListener('langchange', onLangChange);
    };
  }, []);

  // Filter all posts to published only — PostList also filters, but App
  // pre-filters so the post lookup in the post-view route is publication-safe.
  var allPosts = window.POSTS || [];
  var now = new Date();
  var filteredPosts = allPosts.filter(function(p) {
    return new Date(p.published_at) <= now;
  });

  // --- Routing ----------------------------------------------------
  var content;

  if (route.view === 'about') {
    content = React.createElement(window.About, null);

  } else if (route.view === 'post') {
    // Allowlist check: slug must exist in known published posts.
    // Unknown slug → post is undefined → PostView renders "Post not found" (fixed i18n string).
    // SECURITY: raw slug is NEVER reflected into the DOM in the error state —
    // PostView renders i18n.post_not_found only.
    var post = filteredPosts.find(function(p) { return p.slug === route.slug; });
    content = React.createElement(window.PostView, {
      post:   post,
      onBack: function() { navigate('/'); },
    });

  } else {
    // view === 'home' (with optional activeTag)
    var activeTagInit = route.activeTag || null;
    content = React.createElement(window.PostList, {
      posts:        filteredPosts,
      onSelectPost: function(s) { navigate('/p/' + s); },
      activeTag:    activeTagInit,
      setActiveTag: function(tag) {
        if (tag) {
          navigate('/tag/' + tag);
        } else {
          navigate('/');
        }
      },
    });
  }

  return (
    <div className="site-wrapper">
      {React.createElement(window.Header, null)}
      <div className="content-area">
        {content}
      </div>
      {React.createElement(window.Footer, null)}
    </div>
  );
}

// Mount — React 18 createRoot API.
ReactDOM.createRoot(document.getElementById('root')).render(
  React.createElement(App, null)
);
