import { isString } from '@bamboocss/shared'
import type { Context } from './context'

export class FileEngine {
  constructor(private context: Pick<Context, 'config'>) {}

  private get outExtension() {
    return this.context.config.outExtension
  }

  private get forceConsistentTypeExtension() {
    return this.context.config.forceConsistentTypeExtension || false
  }

  ext(file: string): string {
    return `${file}.${this.outExtension}`
  }

  extDts(file: string): string {
    const dts = this.outExtension === 'mjs' && this.forceConsistentTypeExtension ? 'd.mts' : 'd.ts'
    return `${file}.${dts}`
  }

  /**
   * The specifier a declaration file *imports* by, which is a different question from the
   * name it is written under — but not an independent one.
   *
   * Under the flag the file beside this one is `x.d.mts`, and the specifier has to be
   * `./x.mjs`: TypeScript maps that to `x.d.mts`, whereas `./x.d.mts` is only legal under
   * `allowImportingTsExtensions` and a bare `./x` is probed as `.ts`/`.d.ts`/`.js` and never
   * finds it. This used to emit the `.d.mts` form, so the flag shipped imports that only
   * resolved where the extension was permitted.
   *
   * Without the flag, declarations are `.d.ts` and a bare specifier resolves as it always
   * has.
   */
  private __extDts(file: string): string {
    return this.forceConsistentTypeExtension ? this.ext(file) : file
  }

  import(mod: string, file: string): string {
    return `import { ${mod} } from '${this.ext(file)}';`
  }

  importType(mod: string, file: string): string {
    return `import type { ${mod} } from '${this.__extDts(file)}';`
  }

  exportType(mod: string, file: string): string {
    return `export type { ${mod} } from '${this.__extDts(file)}';`
  }

  /** Re-export named bindings without binding them locally. */
  reExport(mod: string, file: string): string {
    return `export { ${mod} } from '${this.ext(file)}';`
  }

  /**
   * The same, said from a declaration file.
   *
   * Not `exportType`: that emits `export type { … }`, which drops the value side of
   * anything it names. A barrel re-exporting `css` needs the binding, not just its type.
   */
  reExportDts(mod: string, file: string): string {
    return `export { ${mod} } from '${this.__extDts(file)}';`
  }

  exportStar(file: string): string {
    return `export * from '${this.ext(file)}';`
  }

  exportTypeStar(file: string): string {
    return `export * from '${this.__extDts(file)}';`
  }

  isTypeFile(file: string): boolean {
    return file.endsWith('.d.ts') || file.endsWith('.d.mts')
  }

  jsDocComment(comment: string | undefined, options?: { deprecated?: boolean | string; default?: string }): string {
    const { deprecated, default: defaultValue } = options ?? {}
    if (!comment && !deprecated && !defaultValue) return ''

    const comments: string[] = ['/**']

    if (comment) {
      comments.push(` * ${comment}`, '\n')
    }

    if (deprecated) {
      const suffix = isString(deprecated) ? ` ${deprecated}` : ''
      comments.push(` * @deprecated${suffix}`)
    }

    if (defaultValue) {
      comments.push(` * @default ${defaultValue}`)
    }

    comments.push(' */')
    return comments.filter((c) => c.trim().length).join('\n')
  }

  /**
   * convert import type { CompositionStyleObject } from './system-types'
   * to import type { CompositionStyleObject } from './system-types.d.ts'
   */
  rewriteTypeImport(code: string) {
    return code.replace(/import\s+type\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g, this.importType('$1', '$2'))
  }
}
