<!DOCTYPE html>
<html>
  <head>
    <title>{{ title }}</title>
    <script src="/static/socket.io.min.js"></script>
    <link href="/static/fonts.css" rel="stylesheet" type="text/css" />
    <link href="/static/quasar.prod.css" rel="stylesheet" type="text/css" />
    <script src="/static/tailwind.min.js"></script>
    {{ head_html | safe }}
  </head>
  <body>
    <script src="/static/vue.global.prod.js"></script>
    <script src="/static/quasar.umd.prod.js"></script>

    {{ body_html | safe }}

    <div id="app"></div>
    <script type="module">
      const True = true;
      const False = false;
      const None = undefined;

      const elements = {{ elements | safe }};

      function render_recursively(elements, id) {
        const element = elements[id];
        const props = {
          id: element.id,
          ref: 'r' + element.id,
          class: element.class.join(' ') || undefined,
          style: Object.entries(element.style).reduce((str, [p, val]) => `${str}${p}:${val};`, '') || undefined,
          ...element.props,
        };
        Object.entries(element.events).forEach(([type, arg_names]) => {
          const parts = type.split('.');
          const event_name = 'on' + parts[0][0].toLocaleUpperCase() + parts[0].substring(1);
          const modifiers = parts.splice(1);
          props[event_name] = Vue.withModifiers((e) => {
            const all = typeof e !== 'object' || arg_names.includes('*');
            const args = all ? e : Object.fromEntries(arg_names.map(a => [a, e[a]]));
            window.socket.emit("event", {id: element.id, type, args});
          }, modifiers);
        });
        const slots = {};
        Object.entries(element.slots).forEach(([name, ids]) => {
          const children = ids.map(id => render_recursively(elements, id));
          if (name == 'default' && element.text) {
            children.unshift(element.text);
          }
          slots[name] = () => children;
        });
        return Vue.h(Vue.resolveComponent(element.tag), props, slots);
      }

      function run_javascript(code, request_id) {
        const result = eval(code);
        if (request_id) {
          window.socket.emit("javascript_response", {request_id, result});
        }
      }

      const app = Vue.createApp({
        data() {
          return {
            elements,
          };
        },
        render() {
          return render_recursively(this.elements, 0);
        },
        mounted() {
          const query = { client_id: {{ client_id }} };
          window.socket = io("{{ socket_address }}", { path: "/ws/socket.io", query });
          window.socket.on("update", (msg) => {
            Object.entries(msg.elements).forEach(([id, element]) => this.elements[element.id] = element);
          });
          window.socket.on("run_method", (msg) => this.$refs['r' + msg.id][msg.name](...msg.args));
          window.socket.on("run_javascript", (msg) => run_javascript(msg['code'], msg['request_id']));
          window.socket.on("notify", (msg) => Quasar.Notify.create(msg));
          window.socket.on("disconnect", () => window.location.reload());
        },
      }).use(Quasar);

      {{ vue_scripts | safe }}
      {{ js_imports | safe }}

      const dark = {{ dark }};
      Quasar.Dark.set(dark === None ? "auto": dark);

      app.mount("#app");
    </script>
  </body>
</html>
