//
// Slow-repo guard for Snapshot.track.
//
// On huge repositories (e.g. 270k+ untracked files in intellij-community)
// the initial `git add --pathspec-from-file=-` staged by the snapshot system
// can take tens of seconds — long enough to wedge the turn before the LLM
// is even called. This wrapper:
//
//   1. Runs the real `track()` in a forked fiber.
//   2. Waits up to `TIMEOUT_MS` for it to complete.
//   3. If it times out AND we have a sessionID to target, asks the user:
//        - "Continue with snapshots": keep waiting on this turn; snapshot
//          finishes eventually and undo/redo stays functional. Future turns
//          are fast because the snapshot index is built.
//        - "Disable for this project": interrupt the in-flight snapshot,
//          persist `"snapshot": false` to `.saeeol/saeeol.json`, and skip. All
//          future sessions on this project load with snapshots off.
//        - Dismissed / no sessionID: interrupt and skip. Mark the instance
//          so we don't prompt again until the instance reloads.
//
// While the snapshot is running, we inject a synthetic text part into the
// live assistant message so the user sees an "Initializing snapshot…" line
// in the chat — the same place bash/edit tool calls render. The part is
// removed when the snapshot finishes, so the chat history stays clean.
//
// All of this is Saeeol-specific — the upstream snapshot module remains a thin
// shim that calls into here.

import { Duration, Effect, Fiber } from "effect"
import * as Log from "@saeeol/core/util/log"
import { SaeeolSnapshotTrack as T } from "./track-types"
import { SaeeolSnapshotTrack as H } from "./track-hooks"

/**
 * Re-exported namespace that merges track-types + track-hooks + wrap().
 */
export namespace SaeeolSnapshotTrack {
  export type Answer = T.Answer
  export type Hooks = T.Hooks
  export type WrapInput = T.WrapInput
  export type ProgressHandle = T.ProgressHandle
  export type State = T.State
  export const TIMEOUT_MS = T.TIMEOUT_MS
  export const ANSWER_CONTINUE = T.ANSWER_CONTINUE
  export const ANSWER_DISABLE = T.ANSWER_DISABLE
  export const PROGRESS_INITIALIZING = T.PROGRESS_INITIALIZING
  export const formatProgress = T.formatProgress
  export const defaultHooks = H.defaultHooks
  const log = Log.create({ service: "snapshot.track" })
  export const makeState = T.makeState
  /** Runs `inner` with a timeout + slow-repo prompt flow. */
  export const wrap = (input: T.WrapInput): Effect.Effect<string | undefined> =>
    Effect.gen(function* () {
      if (input.state.disabledForSession) return undefined

      const hooks = input.hooks ?? H.defaultHooks
      const timeoutMs = input.timeoutMs ?? T.TIMEOUT_MS
      const progressDelayMs = input.progressDelayMs ?? 500

      const canShowProgress = !!(input.sessionID && input.messageID)
      let handle: T.ProgressHandle | undefined
      let frameIdx = 0

      const nextFrameText = () => {
        const frame = T.SPINNER_FRAMES[frameIdx % T.SPINNER_FRAMES.length]
        frameIdx += 1
        return T.formatProgress(T.PROGRESS_INITIALIZING, frame)
      }

      const clearProgress = () =>
        Effect.gen(function* () {
          if (!handle) return
          const h = handle
          handle = undefined
          yield* Effect.promise(() =>
            hooks.endProgress({ handle: h }).catch((err: unknown) => {
              log.warn("failed to clear snapshot progress part", { err })
            }),
          )
        })

      const progressFiber = canShowProgress
        ? yield* Effect.forkChild(
            Effect.gen(function* () {
              yield* Effect.sleep(Duration.millis(Math.min(progressDelayMs, timeoutMs)))
              if (!input.sessionID || !input.messageID) return
              const started = yield* Effect.promise(async () => {
                try {
                  return await hooks.startProgress({
                    sessionID: input.sessionID!,
                    messageID: input.messageID!,
                    text: nextFrameText(),
                  })
                } catch (err) {
                  log.warn("failed to publish snapshot progress part", { err })
                  return undefined
                }
              })
              if (!started) return
              handle = started
              while (true) {
                yield* Effect.sleep(Duration.millis(T.SPINNER_INTERVAL_MS))
                if (!handle) return
                const text = nextFrameText()
                const h = handle
                yield* Effect.promise(() =>
                  hooks.updateProgress({ handle: h, text }).catch((err: unknown) => {
                    log.warn("failed to advance snapshot spinner frame", { err })
                  }),
                )
              }
            }),
          )
        : undefined

      const fiber = yield* Effect.forkChild(input.inner)
      const quick = yield* Fiber.join(fiber).pipe(
        Effect.timeoutOption(Duration.millis(timeoutMs)),
        Effect.catch((err) => {
          log.warn("snapshot track failed", { err })
          return Effect.succeed({ _tag: "Some" as const, value: undefined as string | undefined })
        }),
      )
      if (quick._tag === "Some") {
        if (progressFiber) yield* Fiber.interrupt(progressFiber)
        yield* clearProgress()
        return quick.value
      }

      if (!input.sessionID || input.state.asked) {
        log.warn("snapshot track slow; skipping for this instance", { timeoutMs })
        input.state.disabledForSession = true
        yield* Fiber.interrupt(fiber)
        if (progressFiber) yield* Fiber.interrupt(progressFiber)
        yield* clearProgress()
        return undefined
      }
      input.state.asked = true

      const sessionID = input.sessionID
      const answer = yield* Effect.promise(() => hooks.ask({ sessionID }))

      if (answer === "continue") {
        log.info("user chose to keep waiting for snapshot; joining fiber")
        const finished = yield* Fiber.join(fiber).pipe(
          Effect.catch((err) => {
            log.warn("snapshot track failed after user continue", { err })
            return Effect.succeed(undefined as string | undefined)
          }),
        )
        if (progressFiber) yield* Fiber.interrupt(progressFiber)
        yield* clearProgress()
        if (finished) input.state.asked = false
        return finished
      }

      yield* Fiber.interrupt(fiber)
      if (progressFiber) yield* Fiber.interrupt(progressFiber)
      input.state.disabledForSession = true

      if (answer === "disable") {
        log.info("user chose to disable snapshot for this project")
        yield* Effect.promise(() =>
          hooks.persistDisable().catch((err: unknown) => {
            log.error("failed to persist snapshot:false to project config", { err })
          }),
        )
      } else {
        log.info("user dismissed snapshot prompt; disabling for this instance only")
      }

      yield* clearProgress()
      return undefined
    })
}
