const EXTERNAL_ASSET_RE = /^(?:data:|https?:|blob:)/i;
const FILE_URL_RE = /^file:\/\//i;
const HTTP_PROTOCOL_RE = /^https?:$/i;
const LOCAL_IMAGE_EXT_RE = /\.(?:png|jpe?g|gif|webp|bmp|svg|avif)$/i;
// Same-origin server routes the dev server / Nitro serves directly.
// These are NOT file-system asset paths — they're runtime endpoints
// (`/api/ai/image-proxy?url=...`, `/api/local-asset?path=...`, etc).
// Without this carve-out the asset resolver treats them as local
// file paths and double-wraps them through `/api/local-asset`,
// producing a broken `/api/local-asset?path=%2Fapi%2Fai%2Fimage-proxy%3F...`
// URL that only the local-asset handler can dispatch on, which then
// 404s because `/api/ai/image-proxy?...` isn't a real file path.
const SAME_ORIGIN_ROUTE_RE = /^\/(?:api|_)\//;

export interface RuntimeAssetSource {
  sourcePath: string | null;
  runtimeUrl: string | null;
  isLocal: boolean;
  unresolved: boolean;
}

export function isLocalAssetPath(assetPath: string | null | undefined): boolean {
  if (!assetPath) return false;
  const trimmed = assetPath.trim();
  if (EXTERNAL_ASSET_RE.test(trimmed)) return false;
  if (SAME_ORIGIN_ROUTE_RE.test(trimmed)) return false;
  return true;
}

export function resolveRuntimeAssetSource(
  assetPath: string | null | undefined,
  documentPath: string | null | undefined,
): RuntimeAssetSource {
  if (!assetPath) {
    return {
      sourcePath: null,
      runtimeUrl: null,
      isLocal: false,
      unresolved: false,
    };
  }

  if (!isLocalAssetPath(assetPath)) {
    return {
      sourcePath: null,
      runtimeUrl: assetPath,
      isLocal: false,
      unresolved: false,
    };
  }

  if (FILE_URL_RE.test(assetPath)) {
    const sourcePath = normalizePath(decodeFileUrl(assetPath));
    return createLocalRuntimeAssetSource(sourcePath);
  }

  if (isAbsolutePath(assetPath)) {
    const sourcePath = normalizePath(assetPath);
    return createLocalRuntimeAssetSource(sourcePath);
  }

  if (!documentPath) {
    return {
      sourcePath: null,
      runtimeUrl: null,
      isLocal: true,
      unresolved: true,
    };
  }

  const sourcePath = resolveDocumentAssetPath(documentPath, assetPath);
  return createLocalRuntimeAssetSource(sourcePath);
}

export function resolveDocumentAssetPath(documentPath: string, assetPath: string): string {
  if (FILE_URL_RE.test(assetPath)) {
    return normalizePath(decodeFileUrl(assetPath));
  }
  if (isAbsolutePath(assetPath)) {
    return normalizePath(assetPath);
  }

  const baseDir = getDirname(documentPath);
  return joinNormalizedPath(baseDir, assetPath);
}

export function toStoredAssetPath(
  assetPath: string,
  documentPath: string | null | undefined,
): string {
  if (!assetPath) return assetPath;
  if (!isLocalAssetPath(assetPath)) return assetPath;

  const normalizedAssetPath = FILE_URL_RE.test(assetPath)
    ? normalizePath(decodeFileUrl(assetPath))
    : normalizePath(assetPath);

  if (!isAbsolutePath(normalizedAssetPath) || !documentPath) {
    return normalizedAssetPath;
  }

  const relative = toRelativePath(getDirname(documentPath), normalizedAssetPath);
  return relative ?? normalizedAssetPath;
}

export function toFileUrl(filePath: string): string {
  const normalized = normalizePath(filePath);
  if (normalized.startsWith('//')) {
    return `file:${encodeURI(normalized)}`;
  }
  if (/^[A-Za-z]:\//.test(normalized)) {
    return `file:///${encodeURI(normalized)}`;
  }
  if (normalized.startsWith('/')) {
    return `file://${encodeURI(normalized)}`;
  }
  return encodeURI(normalized);
}

export function toRuntimeLocalAssetUrl(filePath: string): string {
  return toLocalAssetBridgeUrl(filePath) ?? toFileUrl(filePath);
}

export function toLocalAssetBridgeUrl(filePath: string): string | null {
  const runtimeOrigin = getRuntimeOrigin();
  if (!runtimeOrigin) return null;
  const normalized = normalizePath(filePath);
  return `${runtimeOrigin}/api/local-asset?path=${encodeURIComponent(normalized)}`;
}

function toRelativePath(baseDir: string, targetPath: string): string | null {
  const normalizedBase = normalizePath(baseDir);
  const normalizedTarget = normalizePath(targetPath);
  const basePrefix = extractPathPrefix(normalizedBase);
  const targetPrefix = extractPathPrefix(normalizedTarget);

  if (basePrefix.toLowerCase() !== targetPrefix.toLowerCase()) {
    return null;
  }

  const baseParts = stripPrefix(normalizedBase, basePrefix).split('/').filter(Boolean);
  const targetParts = stripPrefix(normalizedTarget, targetPrefix).split('/').filter(Boolean);

  let shared = 0;
  while (
    shared < baseParts.length &&
    shared < targetParts.length &&
    baseParts[shared].toLowerCase() === targetParts[shared].toLowerCase()
  ) {
    shared += 1;
  }

  const up: string[] = Array.from({ length: baseParts.length - shared }, () => '..');
  const down = targetParts.slice(shared);
  const relative = [...up, ...down].join('/');
  return relative || '.';
}

function decodeFileUrl(url: string): string {
  const decoded = decodeURIComponent(url.replace(/^file:\/\/+/i, ''));
  return decoded.replace(/^\/([A-Za-z]:)/, '$1');
}

function getRuntimeOrigin(): string | null {
  if (typeof window === 'undefined' || !window.location) return null;
  if (!HTTP_PROTOCOL_RE.test(window.location.protocol)) return null;
  const origin = window.location.origin?.trim();
  return origin && origin !== 'null' ? origin.replace(/\/+$/, '') : null;
}

function isAbsolutePath(value: string): boolean {
  return /^[A-Za-z]:[\\/]/.test(value) || value.startsWith('\\\\') || value.startsWith('/');
}

function normalizePath(value: string): string {
  const path = value.replace(/\\/g, '/');
  return path.startsWith('//')
    ? `//${path.slice(2).replace(/\/+/g, '/')}`
    : path.replace(/\/+/g, '/');
}

function createLocalRuntimeAssetSource(sourcePath: string): RuntimeAssetSource {
  return {
    sourcePath,
    runtimeUrl: canBridgeLocalImagePath(sourcePath) ? toRuntimeLocalAssetUrl(sourcePath) : null,
    isLocal: true,
    unresolved: false,
  };
}

function hasSupportedLocalImageExtension(filePath: string): boolean {
  const normalized = normalizePath(filePath).split(/[?#]/, 1)[0];
  return LOCAL_IMAGE_EXT_RE.test(normalized);
}

function canBridgeLocalImagePath(filePath: string): boolean {
  if (hasSupportedLocalImageExtension(filePath)) return true;
  return !hasExplicitFileExtension(filePath);
}

function hasExplicitFileExtension(filePath: string): boolean {
  const normalized = normalizePath(filePath).split(/[?#]/, 1)[0];
  const segments = normalized.split('/');
  const lastSegment = segments[segments.length - 1] ?? '';
  const dotIndex = lastSegment.lastIndexOf('.');
  return dotIndex > 0 && dotIndex < lastSegment.length - 1;
}

function getDirname(filePath: string): string {
  const normalized = normalizePath(filePath).replace(/\/+$/, '');
  const lastSlash = normalized.lastIndexOf('/');
  if (lastSlash <= 0) {
    return normalized.includes(':') ? `${normalized.split(':')[0]}:/` : '/';
  }
  return normalized.slice(0, lastSlash);
}

function joinNormalizedPath(baseDir: string, relativePath: string): string {
  const normalizedBase = normalizePath(baseDir);
  const normalizedRelative = normalizePath(relativePath);
  const prefix = extractPathPrefix(normalizedBase);
  const baseSegments = stripPrefix(normalizedBase, prefix).split('/').filter(Boolean);
  const relativeSegments = normalizedRelative.split('/').filter(Boolean);

  const parts = [...baseSegments];
  for (const segment of relativeSegments) {
    if (segment === '.') continue;
    if (segment === '..') {
      if (parts.length > 0) parts.pop();
      continue;
    }
    parts.push(segment);
  }

  if (!prefix) return parts.join('/');
  const joined = parts.join('/');
  if (prefix === '/') return joined ? `/${joined}` : '/';
  if (prefix.startsWith('//')) return joined ? `${prefix}/${joined}` : prefix;
  return joined ? `${prefix}/${joined}` : `${prefix}/`;
}

function extractPathPrefix(path: string): string {
  if (path.startsWith('//')) {
    const parts = path.split('/').filter(Boolean);
    if (parts.length >= 2) {
      return `//${parts[0]}/${parts[1]}`;
    }
    return '//';
  }
  const driveMatch = path.match(/^[A-Za-z]:/);
  if (driveMatch) return driveMatch[0];
  if (path.startsWith('/')) return '/';
  return '';
}

function stripPrefix(path: string, prefix: string): string {
  if (!prefix) return path;
  if (prefix === '/') return path.slice(1);
  if (prefix.startsWith('//')) return path.slice(prefix.length);
  return path.slice(prefix.length).replace(/^\/+/, '');
}
