import client from 'part:@sanity/base/client'
import Observable from '@sanity/observable'
import debounceCollect from './utils/debounceCollect'

let _globalListener
const getGlobalListener = () => {
  if (!_globalListener) {
    _globalListener = Observable
      .from(client.listen('*[!(_id in path("_.**"))]', {}, {includeResult: false}))
      .share()
  }
  return _globalListener
}

function listen(id) {
  return Observable.of({type: 'welcome', documentId: id})
    .concat(getGlobalListener())
    .filter(event => event.documentId === id)
}

function fetchAllDocumentSnapshots(selections) {
  const optimizedParams = {}
  const queryParts = selections.map(([id, paths], queryIndex) => {
    optimizedParams[[`id_${queryIndex}`]] = id
    return `*[_id==$id_${queryIndex}]{_id,_type,${paths.join(',')}}`
  })

  const optimizedQuery = `[${queryParts.join(',\n')}]`

  return client.observable.fetch(optimizedQuery, optimizedParams)
    .map(result => result.map(res => res[0]))
}

const debouncedFetchDocumentSnapshot = debounceCollect(fetchAllDocumentSnapshots, 50)

// todo: keep for debugging purposes for now
// function fetchDocumentSnapshot(id, selection) {
//   return client.observable.fetch(`*[_id==$id]{_id,_type,${selection.join(',')}}`, {id})
//     .map(result => result[0])
// }

export default function observeWithPaths(id, paths) {
  return debouncedFetchDocumentSnapshot(id, paths)
    .concat(listen(id)
      .debounceTime(1000)
      .switchMap(event => debouncedFetchDocumentSnapshot(id, paths)))
}

@@ -1,12 +1,60 @@
+import %7Bvalues, sortBy, identity%7D from 'lodash'%0A
 import clien
@@ -605,85 +605,118 @@
 ion 
-fetchAllDocumentSnapshots(selections) %7B%0A  const optimizedParams
+combineSelections(selections) %7B%0A  return values(selections.reduce((output, %5Bid, paths%5D, index)
  =
+%3E
  %7B
-%7D
 %0A
+  
   const 
 quer
@@ -715,227 +715,707 @@
 nst 
-queryParts = selections.map((%5Bid, paths%5D, queryIndex) =%3E %7B%0A    optimizedParams%5B%5B%60id_$%7BqueryIndex%7D%60%5D%5D = id%0A    return
+key = sortBy(paths.join(','), identity)%0A    if (!output%5Bkey%5D) %7B%0A      output%5Bkey%5D = %7Bpaths, ids: %5B%5D, map: %5B%5D%7D%0A    %7D%0A    const idx = output%5Bkey%5D.ids.length%0A    output%5Bkey%5D.ids%5Bidx%5D = id%0A    output%5Bkey%5D.map%5Bidx%5D = index%0A    return output%0A  %7D, %7B%7D))%0A%7D%0A%0Afunction stringifyId(id) %7B%0A  return JSON.stringify(id)%0A%7D%0Afunction toSubQuery(%7Bids, paths%7D) %7B%0A  const stringifiedIds = ids.map(stringifyId)%0A  const filter = stringifiedIds.length === 1%0A    ?
  %60*%5B_id
+ 
 ==
-$id_$%7BqueryIndex%7D%5D%7B_id,_type,$%7Bpaths.join(',')%7D%7D%60%0A  %7D)%0A%0A  const optimizedQuery = %60%5B$%7BqueryPart
+ $%7BstringifiedIds%5B0%5D%7D%5D%60%0A    : %60*%5B_id in %5B$%7BstringifiedIds.join(',')%7D%5D%5D%60%0A  return %60$%7Bfilter%7D%7B_id,_type,$%7Bpaths.join(',')%7D%7D%60%0A%7D%0A%0Afunction toGradientQuery(combinedSelections) %7B%0A  const subQueries = combinedSelections.map(toSubQuery)%0A  return %60%5B$%7BsubQuerie
 s.jo
@@ -1423,126 +1423,617 @@
 n(',
-%5Cn
 ')%7D%5D%60%0A
-%0A  return client.observable.fetch(optimizedQuery, optimizedParams)%0A    .map(result =%3E result.map(res =%3E res%5B0%5D
+%7D%0A%0Afunction reproject(queryResult, combinedSelections) %7B%0A  return queryResult.reduce((reprojected, subResult, index) =%3E %7B%0A    const map = combinedSelections%5Bindex%5D.map%0A    map.forEach((resultIdx, i) =%3E %7B%0A      const id = combinedSelections%5Bindex%5D.ids%5Bi%5D%0A      reprojected%5BresultIdx%5D = subResult.find(doc =%3E doc._id === id)%0A    %7D)%0A    return reprojected%0A  %7D, %5B%5D)%0A%7D%0A%0Afunction fetchAllDocumentSnapshots(selections) %7B%0A  const combinedSelections = combineSelections(selections)%0A  return client.observable%0A    .fetch(toGradientQuery(combinedSelections))%0A    .map(result =%3E reproject(result, combinedSelections
 ))%0A%7D

Optimize preview snapshot fetching (#468)