import path from "path"
import os from "os"
import { existsSync } from "fs"
import { mkdir } from "fs/promises"
import { fileURLToPath } from "url"
import { UI } from "@/cli/ui"

export type Shell = "zsh" | "bash" | "fish" | "powershell"

export const MARKER_START = "# >>> saeeoldev launcher >>>"
export const MARKER_END = "# <<< saeeoldev launcher <<<"

export async function safeDetectRepo(): Promise<string | undefined> {
  try {
    return await detectRepo()
  } catch (err) {
    UI.error(err instanceof Error ? err.message : String(err))
    process.exitCode = 1
    return undefined
  }
}

export function parse(value: unknown): Shell {
  if (value === "bash") return value
  if (value === "fish") return value
  if (value === "powershell") return value
  return "zsh"
}

export function detectShell(): Shell {
  if (process.platform === "win32") return "powershell"
  const sh = path.basename(process.env.SHELL ?? "")
  if (sh === "zsh") return "zsh"
  if (sh === "fish") return "fish"
  if (sh === "bash") return "bash"
  return "zsh"
}

export function defaultRc(shell: Shell): string {
  const home = os.homedir()
  if (shell === "zsh") return path.join(home, ".zshrc")
  if (shell === "fish") return path.join(home, ".config", "fish", "config.fish")
  if (shell === "powershell") {
    if (process.platform === "win32") {
      const ps7 = path.join(home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1")
      const wps = path.join(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1")
      if (existsSync(ps7)) return ps7
      if (existsSync(wps)) return wps
      const psmp = process.env.PSMODULEPATH ?? process.env.PSModulePath ?? ""
      const first = (psmp.split(";")[0] ?? "").toLowerCase()
      if (first.includes("\\windowspowershell\\")) return wps
      if (first.includes("\\powershell\\")) return ps7
      return wps
    }
    return path.join(home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1")
  }
  return path.join(home, ".bashrc")
}

export function aliasLine(shell: Shell, repo: string): string {
  const sh = path.join(repo, "bin", "saeeoldev")
  const bat = path.join(repo, "bin", "saeeoldev.cmd")
  const q = (s: string) => `'${s.replaceAll("'", `'\\''`)}'`
  if (shell === "fish") return `alias saeeoldev ${q(sh)}`
  if (shell === "powershell") return `function saeeoldev { & '${bat.replaceAll("'", "''")}' @args }`
  return `alias saeeoldev=${q(sh)}`
}

export function reloadHint(shell: Shell, rc: string): string {
  if (shell === "fish") return `source ${tildify(rc)}`
  if (shell === "powershell") return `. $PROFILE`
  if (shell === "zsh") return `exec zsh`
  return `exec bash`
}

export function tildify(p: string): string {
  const home = os.homedir()
  if (p.startsWith(home)) return "~" + p.slice(home.length)
  return p
}

export async function readMarkerBlock(rc: string): Promise<{ line: string } | undefined> {
  const file = Bun.file(rc)
  if (!(await file.exists())) return undefined
  const text = await file.text()
  const start = text.indexOf(MARKER_START)
  if (start === -1) return undefined
  const end = text.indexOf(MARKER_END, start)
  if (end === -1) return undefined
  const body = text.slice(start + MARKER_START.length, end).trim()
  return { line: body }
}

export async function applyBlock(rc: string, snippet: string): Promise<{ created: boolean; backup?: string }> {
  const file = Bun.file(rc)
  const exists = await file.exists()
  const prev = exists ? await file.text() : ""
  const block = `${MARKER_START}\n${snippet}\n${MARKER_END}\n`

  if (!exists) await mkdir(path.dirname(rc), { recursive: true })
  await Bun.write(rc, replaceOrAppend(prev, block))
  if (!exists) return { created: true }

  const backup = `${rc}.saeeoldev.bak.${stamp()}`
  await Bun.write(backup, prev)
  return { created: false, backup }
}

function replaceOrAppend(prev: string, block: string): string {
  const start = prev.indexOf(MARKER_START)
  if (start === -1) {
    const sep = prev.length === 0 || prev.endsWith("\n") ? "" : "\n"
    return `${prev}${sep}${prev.length === 0 ? "" : "\n"}${block}`
  }
  const end = prev.indexOf(MARKER_END, start)
  if (end === -1) return `${prev}\n${block}`
  const after = end + MARKER_END.length
  const tail = prev.slice(after).replace(/^\n/, "")
  return prev.slice(0, start) + block + (tail ? tail : "")
}

function stamp(): string {
  const d = new Date()
  const pad = (n: number) => String(n).padStart(2, "0")
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`
}

export async function findRepoFrom(start: string): Promise<string | undefined> {
  let dir = start
  while (true) {
    const candidate = path.join(dir, "packages", "saeeol", "package.json")
    if (await Bun.file(candidate).exists()) return dir
    const parent = path.dirname(dir)
    if (parent === dir) return undefined
    dir = parent
  }
}

function isBunfsPath(p: string): boolean {
  if (p.startsWith("/$bunfs/")) return true
  if (/^[A-Za-z]:[\\/]~BUN[\\/]/.test(p)) return true
  return false
}

export async function detectRepo(): Promise<string> {
  const hint = process.env.SAEEOL_DEV_REPO
  if (hint) return hint

  const candidates: string[] = []

  const meta = (() => {
    try {
      return fileURLToPath(import.meta.url)
    } catch {
      return undefined
    }
  })()
  if (meta && !isBunfsPath(meta)) candidates.push(path.dirname(meta))

  if (process.execPath) candidates.push(path.dirname(process.execPath))
  candidates.push(process.cwd())

  for (const start of candidates) {
    const found = await findRepoFrom(start)
    if (found) return found
  }

  throw new Error(
    "cannot locate saeeol source checkout; set SAEEOL_DEV_REPO=/path/to/saeeol or run ./bin/saeeoldev dev-setup from the repo",
  )
}
