import { Binary } from "@novaclaw/core/util/binary"
import type { SessionV2Info as Session } from "@novaclaw/sdk/v2/client"
import { createMemo } from "solid-js"
import { produce, reconcile, type SetStoreFunction } from "solid-js/store"
import type { createServerSdkContext } from "./server-sdk"
import type { createServerSyncContextInner } from "./server-sync"
import type { State } from "./global-sync/types"

const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
const sessionFields = new Set([
  "session_status",
  "session_working",
  "session_diff",
  "todo",
  "permission",
  "question",
])

export const createDirSyncContext = (
  directory: string,
  serverSync: ReturnType<typeof createServerSyncContextInner>,
  serverSDK: ReturnType<typeof createServerSdkContext>,
) => {
  const client = serverSDK.createClient({ directory, throwOnError: true })
  const current = createMemo(() => serverSync.child(directory, { mcp: true }))
  const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/")
  const data = new Proxy({} as State, {
    get(_, property: keyof State) {
      if (property === "session_working") return serverSync.session.data.session_working.bind(serverSync.session.data)
      if (sessionFields.has(property)) return serverSync.session.data[property as keyof typeof serverSync.session.data]
      return current()[0][property]
    },
  })
  const set = ((...input: unknown[]) => {
    if (typeof input[0] === "string" && sessionFields.has(input[0])) {
      return (serverSync.session.set as (...args: unknown[]) => unknown)(...input)
    }
    const result = (current()[1] as (...args: unknown[]) => unknown)(...input)
    if (input[0] === "session") current()[0].session.forEach(serverSync.session.remember)
    return result
  }) as SetStoreFunction<State>

  const index = (sessionID: string) => {
    const session = serverSync.session.get(sessionID)
    if (!session || session.location.directory !== directory) return
    const [store, setStore] = current()
    const result = Binary.search(store.session, session.id, (item) => item.id)
    if (result.found) {
      setStore("session", result.index, reconcile(session))
      return
    }
    setStore(
      "session",
      produce((draft) => void draft.splice(result.index, 0, session)),
    )
  }

  return {
    data,
    set,
    get status() {
      return current()[0].status
    },
    get ready() {
      return current()[0].status !== "loading"
    },
    session: {
      remember(session: Session) {
        serverSync.session.remember(session)
        index(session.id)
      },
      get(sessionID: string) {
        const session = serverSync.session.get(sessionID)
        if (session?.location.directory === directory) return session
      },
      async sync(sessionID: string, options?: { force?: boolean }) {
        await serverSync.session.sync(sessionID, options)
        index(sessionID)
      },
      diff: serverSync.session.diff,
      todo: serverSync.session.todo,
      evict(sessionID: string) {
        serverSync.session.evict(sessionID)
      },
      fetch: async (count = 10) => {
        const [store, setStore] = current()
        setStore("limit", (value) => value + count)
        const response = await client.v2.session.list()
        const sessions = [...(response.data?.data ?? [])]
          .filter((session) => !!session?.id)
          .sort((a, b) => cmp(a.id, b.id))
          .slice(0, store.limit)
        sessions.forEach(serverSync.session.remember)
        setStore("session", reconcile(sessions, { key: "id" }))
      },
      more: createMemo(() => current()[0].session.length >= current()[0].limit),
      archive: async (sessionID: string) => {
        await serverSDK.client.v2.session.update({ sessionID, archived: Date.now() })
        current()[1](
          "session",
          produce((draft) => {
            const match = Binary.search(draft, sessionID, (session) => session.id)
            if (match.found) draft.splice(match.index, 1)
          }),
        )
      },
    },
    mcp: {
      toggle: (name: string) => serverSync.mcp.toggle(directory, name),
    },
    absolute,
    get directory() {
      return current()[0].path.directory
    },
  }
}
