import { convertFileSrc } from '@tauri-apps/api/core'
import { canWritePathToVault } from './vaultPathContainment'

const LOCALHOST_ASSET_URL_PREFIX = 'asset://localhost/'
const HTTP_ASSET_URL_PREFIX = 'http://asset.localhost/'
const GENERIC_ASSET_URL_PREFIX = 'asset://'
const ASSET_URL_PREFIXES = [
  LOCALHOST_ASSET_URL_PREFIX,
  HTTP_ASSET_URL_PREFIX,
  GENERIC_ASSET_URL_PREFIX,
]
const ATTACHMENTS_SEGMENT = '/attachments/'
const RELATIVE_ATTACHMENTS_PREFIX = 'attachments/'
const WINDOWS_EXTENDED_PATH_PREFIX = '\\\\?\\'
const WINDOWS_EXTENDED_UNC_PREFIX = '\\\\?\\UNC\\'
const WINDOWS_DRIVE_PATH_PATTERN = /^[A-Za-z]:[\\/]/

type AbsolutePath = string
type AttachmentPath = string
type VaultPath = string

type UrlRequest = {
  url: string
}

type PathRequest = {
  path: string
}

type VaultPathRequest = {
  vaultPath: VaultPath
}

type AttachmentPathRequest = {
  attachmentPath: AttachmentPath
}

export type VaultAttachmentPathRequest = UrlRequest & {
  vaultPath?: VaultPath
}

export type VaultAttachmentUrlRequest = UrlRequest

function safeDecode(value: string): string {
  try {
    return decodeURIComponent(value)
  } catch {
    return value
  }
}

function hasUnsafeRelativeSegment({ path }: PathRequest): boolean {
  return path.split(/[\\/]/u).some(segment => segment === '..')
}

function usesWindowsSeparators({ path }: PathRequest): boolean {
  return WINDOWS_DRIVE_PATH_PATTERN.test(path) || path.startsWith('\\\\')
}

function relativePathForVault({
  attachmentPath,
  vaultPath,
}: AttachmentPathRequest & VaultPathRequest): AttachmentPath {
  return usesWindowsSeparators({ path: vaultPath })
    ? attachmentPath.replace(/\//g, '\\')
    : attachmentPath.replace(/\\/g, '/')
}

function removeWindowsExtendedPrefix(path: AbsolutePath): AbsolutePath {
  if (path.startsWith(WINDOWS_EXTENDED_UNC_PREFIX)) {
    return `\\\\${path.slice(WINDOWS_EXTENDED_UNC_PREFIX.length)}`
  }
  if (path.startsWith(WINDOWS_EXTENDED_PATH_PREFIX)) {
    return path.slice(WINDOWS_EXTENDED_PATH_PREFIX.length)
  }
  return path
}

function normalizedFilesystemPath(path: AbsolutePath): AbsolutePath {
  return removeWindowsExtendedPrefix(path).replace(/\\/g, '/')
}

function withoutTrailingSlash(path: AbsolutePath): AbsolutePath {
  return path.replace(/\/+$/, '')
}

function normalizeForComparison(path: AbsolutePath, vaultPath: VaultPath): AbsolutePath {
  const normalizedPath = withoutTrailingSlash(normalizedFilesystemPath(path))
  const normalizedVaultPath = withoutTrailingSlash(normalizedFilesystemPath(vaultPath))
  return WINDOWS_DRIVE_PATH_PATTERN.test(normalizedPath)
    || WINDOWS_DRIVE_PATH_PATTERN.test(normalizedVaultPath)
    ? normalizedPath.toLowerCase()
    : normalizedPath
}

function assetUrlPrefix({ url }: UrlRequest): string | null {
  return ASSET_URL_PREFIXES.find(prefix => url.startsWith(prefix)) ?? null
}

function isRootedPath({ path }: PathRequest): boolean {
  if (path.startsWith('/')) return true
  if (WINDOWS_DRIVE_PATH_PATTERN.test(path)) return true
  return path.startsWith('\\\\')
}

function restoreUnixRoot({ path }: PathRequest): AbsolutePath {
  return isRootedPath({ path }) ? path : `/${path}`
}

function resolveAssetPath({ url }: UrlRequest): AbsolutePath | null {
  const prefix = assetUrlPrefix({ url })
  if (!prefix) return null
  return restoreUnixRoot({ path: safeDecode(url.slice(prefix.length)) })
}

function extractPortableAttachmentPath({ path }: PathRequest): AttachmentPath | null {
  const normalizedPath = normalizedFilesystemPath(path)
  const index = normalizedPath.lastIndexOf(ATTACHMENTS_SEGMENT)
  if (index === -1) return null

  const filename = normalizedPath.slice(index + ATTACHMENTS_SEGMENT.length)
  return filename ? `${RELATIVE_ATTACHMENTS_PREFIX}${filename}` : null
}

function currentVaultAttachmentPath({
  path,
  vaultPath,
}: PathRequest & VaultPathRequest): AttachmentPath | null {
  const normalizedPath = normalizedFilesystemPath(path)
  const normalizedVaultPath = withoutTrailingSlash(normalizedFilesystemPath(vaultPath))
  const attachmentsRoot = `${normalizedVaultPath}/${RELATIVE_ATTACHMENTS_PREFIX}`
  const comparablePath = normalizeForComparison(normalizedPath, vaultPath)
  const comparableRoot = normalizeForComparison(attachmentsRoot, vaultPath)
  if (!comparablePath.startsWith(comparableRoot)) return null

  const filename = normalizedPath.slice(attachmentsRoot.length)
  return filename ? `${RELATIVE_ATTACHMENTS_PREFIX}${filename}` : null
}

function isPathInsideVault({
  path,
  vaultPath,
}: PathRequest & VaultPathRequest): boolean {
  return canWritePathToVault(
    removeWindowsExtendedPrefix(path),
    removeWindowsExtendedPrefix(vaultPath),
  )
}

function resolveRelativeAttachmentPath({
  url,
  vaultPath,
}: UrlRequest & VaultPathRequest): AbsolutePath | null {
  if (!url.startsWith(RELATIVE_ATTACHMENTS_PREFIX)) return null

  const attachmentPath = safeDecode(url)
  if (hasUnsafeRelativeSegment({ path: attachmentPath })) return null
  return vaultAttachmentPath({ vaultPath, attachmentPath })
}

export function vaultAttachmentPath({
  attachmentPath,
  vaultPath,
}: AttachmentPathRequest & VaultPathRequest): AbsolutePath {
  const separator = usesWindowsSeparators({ path: vaultPath }) ? '\\' : '/'
  const normalizedAttachmentPath = relativePathForVault({ vaultPath, attachmentPath })
  const joiner = vaultPath.endsWith('/') || vaultPath.endsWith('\\') ? '' : separator
  return `${vaultPath}${joiner}${normalizedAttachmentPath}`
}

export function attachmentAssetUrlFromPath({ path }: PathRequest): string {
  return convertFileSrc(path)
}

export function vaultAttachmentAssetUrl({
  attachmentPath,
  vaultPath,
}: AttachmentPathRequest & VaultPathRequest): string {
  return attachmentAssetUrlFromPath({
    path: vaultAttachmentPath({ vaultPath, attachmentPath }),
  })
}

export function isPortableAttachmentPath({ path }: PathRequest): boolean {
  return path.startsWith(RELATIVE_ATTACHMENTS_PREFIX)
}

export function isTauriAssetUrl({ url }: UrlRequest): boolean {
  return assetUrlPrefix({ url }) !== null
}

export function filesystemPathFromAssetUrl({ url }: UrlRequest): AbsolutePath | null {
  return resolveAssetPath({ url })
}

export function isCurrentVaultAssetUrl({
  url,
  vaultPath,
}: UrlRequest & VaultPathRequest): boolean {
  const path = resolveAssetPath({ url })
  return path ? isPathInsideVault({ path, vaultPath }) : false
}

export function portableAttachmentPathFromCurrentVaultAssetUrl({
  url,
  vaultPath,
}: UrlRequest & VaultPathRequest): AttachmentPath | null {
  const path = resolveAssetPath({ url })
  if (!path || !isPathInsideVault({ path, vaultPath })) return null
  return currentVaultAttachmentPath({ path, vaultPath })
}

export function portableAttachmentPathFromCurrentVaultPath({
  path,
  vaultPath,
}: PathRequest & VaultPathRequest): AttachmentPath | null {
  if (!isPathInsideVault({ path, vaultPath })) return null
  return currentVaultAttachmentPath({ path, vaultPath })
}

export function portableAttachmentPathFromAnyAssetUrl({ url }: UrlRequest): AttachmentPath | null {
  const path = resolveAssetPath({ url })
  return path ? extractPortableAttachmentPath({ path }) : null
}

export function isVaultAttachmentUrl({ url }: VaultAttachmentUrlRequest): boolean {
  const trimmedUrl = url.trim()
  return isPortableAttachmentPath({ path: trimmedUrl })
    || isTauriAssetUrl({ url: trimmedUrl })
}

export function resolveVaultAttachmentPath({
  url,
  vaultPath,
}: VaultAttachmentPathRequest): AbsolutePath | null {
  const trimmedVaultPath = vaultPath?.trim()
  if (!trimmedVaultPath) return null

  const trimmedUrl = url.trim()
  if (!trimmedUrl) return null

  const candidatePath = resolveRelativeAttachmentPath({
    url: trimmedUrl,
    vaultPath: trimmedVaultPath,
  })
    ?? resolveAssetPath({ url: trimmedUrl })
    ?? trimmedUrl

  return isPathInsideVault({ path: candidatePath, vaultPath: trimmedVaultPath }) ? candidatePath : null
}
