/** Local model manager — install, remove, list, configure models on disk */

import path from "path"
import { mkdir, rm, readdir, stat } from "fs/promises"
import { Effect } from "effect"
import * as Log from "@saeeol/core/util/log"
import { Global } from "@saeeol/core/global"
import { iife } from "@/util/iife"
import type { ModelArtifact, BackendType } from "./types"
import * as Hub from "./hub"

const log = Log.create({ service: "local/manager" })

/** Root directory for all local models */
export function modelsDir(): string {
  return path.join(Global.Path.data, "local-models")
}

/** Directory for a specific model artifact */
export function modelDir(artifact: ModelArtifact): string {
  return path.join(modelsDir(), artifact.repo.replace("/", "__"), artifact.filename)
}

/** Directory for a specific backend's data */
export function backendDir(backend: BackendType): string {
  return path.join(modelsDir(), "backends", backend)
}

/** Directory for RAG assets */
export function ragDir(): string {
  return path.join(modelsDir(), "rag")
}

/** Ensure a directory exists */
async function ensure(dir: string): Promise<void> {
  await mkdir(dir, { recursive: true })
}

/** Install a model from HuggingFace Hub */
export async function install(
  artifact: ModelArtifact,
  opts?: { onProgress?: (downloaded: number, total: number) => void; signal?: AbortSignal },
): Promise<string> {
  const dest = modelDir(artifact)
  await ensure(path.dirname(dest))

  const fullPath = path.join(dest, artifact.filename)
  const exists = await stat(fullPath).catch(() => undefined)
  if (exists?.size === artifact.sizeBytes) {
    log.info("model already installed", { repo: artifact.repo, filename: artifact.filename })
    return fullPath
  }

  log.info("installing model", { repo: artifact.repo, filename: artifact.filename })
  await Hub.download(artifact.repo, artifact.filename, fullPath, opts)
  return fullPath
}

/** Remove a model from disk */
export async function uninstall(artifact: ModelArtifact): Promise<void> {
  const dir = modelDir(artifact)
  await rm(dir, { recursive: true, force: true })
  log.info("uninstalled model", { repo: artifact.repo, filename: artifact.filename })
}

/** List all installed models on disk */
export async function list(): Promise<Array<{ repo: string; filename: string; path: string; sizeBytes: number }>> {
  const root = modelsDir()
  const result: Array<{ repo: string; filename: string; path: string; sizeBytes: number }> = []

  const entries = await readdir(root, { withFileTypes: true }).catch(() => [] as import("fs").Dirent[])
  for (const entry of entries) {
    if (!entry.isDirectory()) continue
    if (entry.name === "backends" || entry.name === "rag") continue

    const repo = entry.name.replace("__", "/")
    const repoDir = path.join(root, entry.name)
    const files = await readdir(repoDir).catch(() => [] as string[])

    for (const file of files) {
      const filePath = path.join(repoDir, file)
      const s = await stat(filePath).catch(() => null)
      if (s?.isFile()) {
        result.push({ repo, filename: file, path: filePath, sizeBytes: s.size })
      }
    }
  }

  return result
}

/** Install a RAG asset (embedding model, reranker, vectordb) */
export async function installRAG(
  asset: { repo: string; filename: string; sizeBytes: number },
  opts?: { onProgress?: (downloaded: number, total: number) => void; signal?: AbortSignal },
): Promise<string> {
  const dir = path.join(ragDir(), asset.repo.replace("/", "__"))
  await ensure(dir)

  const fullPath = path.join(dir, asset.filename)
  const exists = await stat(fullPath).catch(() => undefined)
  if (exists?.size === asset.sizeBytes) return fullPath

  await Hub.download(asset.repo, asset.filename, fullPath, opts)
  return fullPath
}

/** Check if a model is installed */
export async function isInstalled(artifact: ModelArtifact): Promise<boolean> {
  const fullPath = path.join(modelDir(artifact), artifact.filename)
  const s = await stat(fullPath).catch(() => undefined)
  return s?.isFile() === true && s.size > 0
}
