import Worker from 'web-worker'
import fileSaver from 'file-saver'
import { themePlugin } from '@freesewing/plugin-theme'
import { pluginI18n } from '@freesewing/plugin-i18n'
import { tilerPlugin } from './plugin-tiler.mjs'
import { capitalize, escapeSvgText, get } from '@freesewing/utils'
import mustache from 'mustache'
import he from 'he'
import yaml from 'js-yaml'

export const exportTypes = {
  exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'legal', 'tabloid'],
  exportForEditing: ['svg', 'pdf'],
  exportAsData: ['json', 'yaml'],
}

export const defaultPrintSettings = (units) => ({
  size: units === 'imperial' ? 'letter' : 'a4',
  orientation: 'portrait',
  margin: units === 'imperial' ? 12.7 : 10,
  coverPage: true,
  cutlist: true,
})

/**
 * Instantiate a pattern that uses plugins theme, i18n, and cutlist
 * @param  {Design} Design    the design to construct the pattern from
 * @param  {Object} settings      the settings
 * @param  {Object} overwrite settings to overwrite settings settings with
 * @param  {string} format    the export format this pattern will be prepared for
 * @param  {function} t         the i18n function
 * @return {Pattern}           a pattern
 */
const themedPattern = (Design, settings, overwrite, format, t) => {
  const pattern = new Design({ ...settings, ...overwrite })

  // add the theme and translation to the pattern
  pattern.use(themePlugin, { stripped: format !== 'svg', skipGrid: ['pages'] })
  pattern.use(pluginI18n, t)

  return pattern
}

/**
 * Handle exporting the draft or settings
 * format: format to export to
 * settings: the settings
 * Design: the pattern constructor for the design to be exported
 * t: a translation function to attach to the draft
 * onComplete: business to perform after a successful export
 * onError: business to perform on error
 * */
export const handleExport = async ({
  format,
  settings,
  Design,
  design,
  ui,
  t,
  startLoading,
  stopLoading,
  stopLoadingFail,
  onComplete,
  onError,
}) => {
  // start the loading indicator
  if (typeof startLoading === 'function') startLoading()

  // get a worker going
  const worker = new Worker(new URL('./export-worker.js', import.meta.url), { type: 'module' })

  /*
   * Guard against settings being false, which happens for
   * fully default designs that do not require measurements
   */
  if (settings === false) settings = {}

  // listen for the worker's message back
  worker.addEventListener('message', (e) => {
    // on success
    if (e.data.success) {
      // save it out
      if (e.data.blob) {
        const fileType = exportTypes.exportForPrinting.indexOf(format) === -1 ? format : 'pdf'
        fileSaver.saveAs(e.data.blob, `freesewing-${design || 'pattern'}.${fileType}`)
      }
      // do additional business
      onComplete && onComplete(e)
      // stop the loader
      if (typeof stopLoading === 'function') stopLoading()
    }
    // on error
    else {
      console.log(e.data.error)
      onError && onError(e)
      // stop the loader
      if (typeof stopLoadingFail === 'function') stopLoadingFail()
    }
  })

  // pdf settings
  const pageSettings = {
    ...defaultPrintSettings(settings.units),
    ...get(ui, 'layout', {}),
  }

  // arguments to pass to the worker
  const workerArgs = { format, settings, pageSettings }

  // data passed to the worker must be JSON serializable, so we can't pass functions or prototypes
  // that means if it's not a data export there's more work to do before we can hand off to the worker
  if (exportTypes.exportAsData.indexOf(format) === -1) {
    settings.embed = false
    // make a pattern instance for export rendering
    const layout = settings.layout || ui.layouts?.print || true
    let pattern = themedPattern(Design, settings, { layout }, format, t)

    // a specified size should override the settings one
    pageSettings.size = format

    try {
      // add pages to pdf exports
      if (!exportTypes.exportForEditing.includes(format)) {
        pattern.use(
          tilerPlugin({
            ...pageSettings,
            printStyle: true,
            renderBlanks: false,
            setPatternSize: true,
          })
        )
      }

      // add the strings that are used on the cover page
      workerArgs.strings = {
        design: capitalize(design),
        tagline: 'Come for the sewing pattern. Stay for the community.',
        url: window.location.href,
        cuttingLayout: 'Cutting layout',
      }

      // Initialize the pattern stores
      pattern.getConfig()

      // Save the measurement set name to pattern stores
      if (settings?.metadata?.setName) {
        pattern.store.set('data.setName', escapeSvgText(settings.metadata.setName))
        for (const store of pattern.setStores)
          store.set('data.setName', escapeSvgText(settings.metadata.setName))
      }

      // draft and render the pattern
      pattern.draft()
      workerArgs.svg = pattern.render()

      // Get coversheet info: setName, settings YAML, version, notes, warnings
      const store = pattern.setStores[pattern.activeSet]
      workerArgs.strings.setName = settings?.metadata?.setName
        ? settings.metadata.setName
        : 'ephemeral'
      const settingsWithoutLayout = structuredClone(settings)
      delete settingsWithoutLayout.layout
      workerArgs.strings.yaml = yaml.dump(settingsWithoutLayout)
      workerArgs.strings.version = store?.data?.version ? store.data.version : ''
      const notes = store?.plugins?.['plugin-annotations']?.flags?.note
        ? store?.plugins?.['plugin-annotations']?.flags?.note
        : []
      const warns = store?.plugins?.['plugin-annotations']?.flags?.warn
        ? store?.plugins?.['plugin-annotations']?.flags?.warn
        : []
      workerArgs.strings.notes = flagsToString(notes, mustache, t)
      workerArgs.strings.warns = flagsToString(warns, mustache, t)

      if (format === 'pdf') pageSettings.size = [pattern.width, pattern.height]

      // add the svg and pages data to the worker args
      workerArgs.pages = pattern.setStores[pattern.activeSet].get('pages')
    } catch (err) {
      console.log(err)
      onError && onError(err)
    }
  }

  // post a message to the worker with all needed data
  worker.postMessage(workerArgs)
}

/**
 * Convert pattern flags to a formatted string for printing
 */
const flagsToString = (flags, mustache, t) => {
  let first = true
  let string = ''
  for (const flag of Object.values(flags)) {
    let title = flag.replace ? mustache.render(t(flag.title), flag.replace) : t(flag.title)
    title = he.decode(title)
    let desc = flag.replace ? mustache.render(t(flag.desc), flag.replace) : t(flag.desc)
    desc = desc.replaceAll('\n\n', '\n')
    desc = desc.replaceAll('\n', ' ')
    desc = he.decode(desc)
    if (!first) string += '\n'
    first = false
    string += '- ' + title + ': ' + desc
  }
  return string
}