<script lang="ts" context="module">
	import { fetchUIState } from "$lib/client/lucifer";
	import type Device from "$lib/models/device";
	import type { UIStatePatch } from "$lib/models/uistate";
	import type UIState from "$lib/models/uistate";
	import { getContext, onMount, setContext } from "svelte";
	import { derived, writable, type Readable } from "svelte/store";

  const ctxKey = {ctx: "StateContext"};

  export interface StateContextData {
    reload(): Promise<void>
    state: Readable<UIState>
    error: Readable<string | null>
    deviceList: Readable<Device[]>
  }

  export function getStateContext(): StateContextData {
    return getContext(ctxKey) as StateContextData;
  }
</script>

<script lang="ts">
  const state = writable<UIState>({
    assignments: {},
    devices: {},
    script: {},
  });
  const error = writable<string | null>(null);

  const deviceList = derived(state, state => {
    if (state == null) {
      return [];
    }

    return Object.keys(state.devices)
      .map(k => state.devices[k])
      .sort((a,b) => a.id.localeCompare(b.id));
  })



  async function reload() {
    error.set(null);

    try {
      const newState = await fetchUIState();
      state.set(newState);
    } catch(err) {
      error.set(err?.toString ? err.toString() : String(err))
    }
  }

  async function connectSocket() {
    let url = `ws://${window.location.host}/subscribe`;
    if (import.meta.env.VITE_LUCIFER4_BACKEND_URL != null) {
      url = import.meta.env.VITE_LUCIFER4_BACKEND_URL.replace("http", "ws") +"/subscribe";
    }

    const socket = new WebSocket(url);

    socket.onmessage = (msg) => {
      const patch: UIStatePatch = JSON.parse(msg.data);

      state.update(s => {
        if (patch.device) {
          if (patch.device.delete) {
            const devices = {...s.devices};
            delete devices[patch.device.id];
            return {...s, devices};
          } else {
            return {
              ...s,
              devices: {
                ...s.devices,
                [patch.device.id]: {
                  ...s.devices[patch.device.id],
                  ...patch.device,
                }
              }
            }
          }
        }

        if (patch.assignment) {
          if (patch.assignment.delete) {
            const assignments = {...s.assignments};
            delete assignments[patch.assignment.id];
            return {...s, assignments};
          } else {
            return {
              ...s,
              assignments: {
                ...s.assignments,
                [patch.assignment.id]: {
                  ...s.assignments[patch.assignment.id],
                  ...patch.assignment,
                }
              }
            }
          }
        }

        if (patch.script) {
          if (patch.script.delete) {
            const scripts = {...s.scripts};
            delete scripts[patch.script.id];
            return {...s, scripts};
          } else {
            return {
              ...s,
              scripts: {
                ...s.scripts,
                [patch.script.id]: {
                  ...s.scripts[patch.script.id],
                  ...patch.script,
                }
              }
            }
          }
        }

        return s;
      })
    }

    socket.onerror = err => {
      console.warn("Socket failed:", err);
      socket.close();
    }

    socket.onclose = () => {
      setTimeout(() => connectSocket(), 3000);
    }
  }

  onMount(() => { 
    reload();
    const interval = setInterval(reload, 60000);

    connectSocket();

    return () => clearInterval(interval);
  });

  setContext<StateContextData>(ctxKey, {
    reload,
    error: { subscribe: error.subscribe },
    state: { subscribe: state.subscribe },
    deviceList,
  });
</script>

<slot></slot>