export * as LocationWatcher from "./location-watcher"

import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
import { Context, Effect, Layer, Stream } from "effect"
import { FileSystem } from "@opencode-ai/schema/filesystem"
import { Document } from "@opencode-ai/schema/config"
import path from "path"
import { Config } from "../config"
import { Bus } from "../bus"
import { FSUtil } from "@opencode-ai/util/fs-util"
import { Git } from "../git"
import { Location } from "../location"
import { Watcher } from "./watcher"
import { Ignore } from "./ignore"
import { Protected } from "./protected"

function protecteds(dir: string) {
  return Protected.paths().filter((item) => {
    const relative = path.relative(dir, item)
    return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative)
  })
}

export interface Interface {}

export class Service extends Context.Service<Service, Interface>()("@opencode/LocationWatcher") {}

const layer = Layer.effect(
  Service,
  Effect.gen(function* () {
    const location = yield* Location.Service
    const watcher = yield* Watcher.Service
    const bus = yield* Bus.Service
    const fs = yield* FSUtil.Service
    const git = yield* Git.Service
    const configService = yield* Config.Service
    const publish = (update: { type: "create" | "update" | "delete"; path: string }) =>
      bus.publish(FileSystem.Event.Changed, {
        file: update.path,
        event: update.type === "create" ? "add" : update.type === "update" ? "change" : "unlink",
      })

    yield* Effect.gen(function* () {
      const config = (yield* configService.entries())
        .filter((entry): entry is Document => entry.type === "document")
        .flatMap((item) => item.info.watcher?.ignore ?? [])
      const home = Protected.isHome(location.directory)

      if (!home && location.vcs) {
        const updates = yield* watcher.subscribe({
          path: location.directory,
          type: "directory",
          ignore: [...Ignore.PATTERNS, ...config, ...protecteds(location.directory)],
        })
        yield* updates.pipe(Stream.runForEach(publish), Effect.forkScoped)
      }
      if (home) {
        yield* Effect.logInfo("location watcher skipped home directory", { directory: location.directory })
      }

      if (location.vcs?.type === "git") {
        const resolved = (yield* git.repo.discover(location.directory))?.gitDirectory
        const vcs = resolved
          ? yield* fs.realPath(resolved).pipe(Effect.catch(() => Effect.succeed(resolved)))
          : undefined
        if (vcs && !config.includes(".git") && !config.includes(vcs) && (!resolved || !config.includes(resolved))) {
          const ignore = (yield* fs.readDirectoryEntries(vcs).pipe(Effect.catch(() => Effect.succeed([])))).flatMap(
            (entry) => (entry.name === "HEAD" ? [] : [entry.name]),
          )
          const updates = yield* watcher.subscribe({ path: vcs, type: "directory", ignore })
          yield* updates.pipe(Stream.runForEach(publish), Effect.forkScoped)
        }
      }
      if (location.vcs?.type === "hg") {
        const store = location.vcs.store
        const vcs = yield* fs.realPath(store).pipe(Effect.catch(() => Effect.succeed(store)))
        if (!config.includes(".hg") && !config.includes(vcs)) {
          const updates = yield* watcher.subscribe({ path: path.join(vcs, "branch"), type: "file" })
          yield* updates.pipe(Stream.runForEach(publish), Effect.forkScoped)
        }
      }
    }).pipe(
      Effect.withSpan("LocationWatcher.start", { attributes: { directory: location.directory } }),
      Effect.catchCause((cause) => Effect.logError("failed to init location watcher service", { cause })),
      Effect.forkScoped,
    )

    return Service.of({})
  }),
)

export const node = makeLocationNode({
  service: Service,
  layer,
  deps: [Watcher.node, FSUtil.node, Location.node, Config.node, Git.node, Bus.node],
})
