// The one spelling a project file name is allowed to have. Browser-safe (no imports): the backend
// (asset tree), the editor (upload/rename) and the CLI (local scan) all apply it, so the same name
// reaches every one of them identically.
//
// Why: an import specifier is matched against a file name character for character (chisel, Monaco,
// the asset tree). A name carrying an invisible or look-alike character therefore produces an import
// that LOOKS correct and resolves to nothing — a file uploaded as "итальянский<NBSP>сквер1.jpg"
// cannot be imported as "итальянский сквер1.jpg", and the two are indistinguishable on screen.
// Canonicalizing at every entry point means a stored name is always the one the user sees and types.

type Range = [ number, number ]

const inRanges = (cp: number, ranges: Range[]) => ranges.some(([ from, to ]) => cp >= from && cp <= to)

/** C0/C1 controls, soft hyphen, zero-width, bidi marks and other invisible format characters. */
const INVISIBLE: Range[] = [
  [ 0x0000, 0x001f ], [ 0x007f, 0x009f ], [ 0x00ad, 0x00ad ], [ 0x061c, 0x061c ], [ 0x180e, 0x180e ],
  [ 0x200b, 0x200f ], [ 0x2028, 0x2029 ], [ 0x202a, 0x202e ], [ 0x2060, 0x2064 ], [ 0x2066, 0x206f ],
  [ 0xfeff, 0xfeff ],
]

/** Every Unicode space that isn't U+0020: NBSP, en/em spaces, narrow NBSP, ideographic space, … */
const ODD_SPACE: Range[] = [
  [ 0x00a0, 0x00a0 ], [ 0x1680, 0x1680 ], [ 0x2000, 0x200a ], [ 0x202f, 0x202f ], [ 0x205f, 0x205f ],
  [ 0x3000, 0x3000 ],
]

/** Path separators + characters no Windows path may hold — `lecodes clone` writes names to disk. */
const ILLEGAL = "/\\:*?\"<>|"

/**
 * Canonical spelling of a single file/folder name (a NAME, not a path — separators are replaced,
 * not split on). Returns "" when nothing printable is left; callers decide whether that's an error.
 */
export const sanitizeAssetName = (name: string): string => {
  let out = ""
  // NFC first: "й" arrives from macOS as и + U+0306 (combining breve) and must collapse to U+0439.
  for (const ch of name.normalize("NFC")) {
    const cp = ch.codePointAt(0)!
    if (inRanges(cp, INVISIBLE)) continue
    out += inRanges(cp, ODD_SPACE) ? " " : ILLEGAL.includes(ch) ? "_" : ch
  }
  return out
    .replace(/ {2,}/g, " ")
    .replace(/^ +/, "")     // leading dots stay — ".gitignore" is a real name
    .replace(/[ .]+$/, "")  // trailing dots/spaces are silently dropped by Windows
}

/** True when `name` is already canonical, i.e. storing it would change nothing. */
export const isCanonicalAssetName = (name: string): boolean => sanitizeAssetName(name) === name
