import { writable } from "svelte/store"

// we assume that the reference to this state object
// will remain for the life of the application
const rootState = {}
const rootStore = writable(rootState)
const contextStores = {}

// contextProviderId is the component id that provides the data for the context
const contextStoreKey = (dataProviderId, childIndex) =>
  `${dataProviderId}${childIndex >= 0 ? ":" + childIndex : ""}`

// creates a store for a datacontext (e.g. each item in a list component)
// overrides store if already exists
const create = (data, dataProviderId, childIndex, parentContextStoreId) => {
  const key = contextStoreKey(dataProviderId, childIndex)
  const state = { data }

  // add reference to parent state object,
  // so we can use bindings like state.parent.parent
  // (if no parent, then parent is rootState )
  state.parent = parentContextStoreId
    ? contextStores[parentContextStoreId].state
    : rootState

  contextStores[key] = {
    store: writable(state),
    subscriberCount: 0,
    state,
    parentContextStoreId,
  }

  return key
}

const subscribe = (subscription, storeKey) => {
  if (!storeKey) {
    return rootStore.subscribe(subscription)
  }
  const contextStore = contextStores[storeKey]

  // we are subscribing to multiple stores,
  // we dont want to run our listener for every subscription, the first time
  // as this could repeatedly run $set on the same component
  // ... which already has its initial properties set properly
  const ignoreFirstSubscription = () => {
    let hasRunOnce = false
    return () => {
      if (hasRunOnce) subscription(contextStore.state)
      hasRunOnce = true
    }
  }

  const unsubscribes = [rootStore.subscribe(ignoreFirstSubscription())]

  // we subscribe to all stores in the hierarchy
  const ancestorSubscribe = ctxStore => {
    // unsubscribe func returned by svelte store
    const svelteUnsub = ctxStore.store.subscribe(ignoreFirstSubscription())

    // we wrap the svelte unsubscribe, so we can
    // cleanup stores when they are no longer subscribed to
    const unsub = () => {
      ctxStore.subscriberCount = contextStore.subscriberCount - 1
      // when no subscribers left, we delete the store
      if (ctxStore.subscriberCount === 0) {
        delete ctxStore[storeKey]
      }
      svelteUnsub()
    }
    unsubscribes.push(unsub)
    if (ctxStore.parentContextStoreId) {
      ancestorSubscribe(contextStores[ctxStore.parentContextStoreId])
    }
  }

  ancestorSubscribe(contextStore)

  // our final unsubscribe function calls unsubscribe on all stores
  return () => unsubscribes.forEach(u => u())
}

const findStore = (dataProviderId, childIndex) =>
  dataProviderId
    ? contextStores[contextStoreKey(dataProviderId, childIndex)].store
    : rootStore

const update = (updatefunc, dataProviderId, childIndex) =>
  findStore(dataProviderId, childIndex).update(updatefunc)

const set = (value, dataProviderId, childIndex) =>
  findStore(dataProviderId, childIndex).set(value)

const getState = contextStoreKey =>
  contextStoreKey ? contextStores[contextStoreKey].state : rootState

const getStore = contextStoreKey =>
  contextStoreKey ? contextStores[contextStoreKey].store : rootStore

export default {
  subscribe,
  update,
  set,
  getState,
  create,
  contextStoreKey,
  getStore,
}
