/**
 * @typedef {import('./types.js').Node} Node
 * @typedef {import('./types.js').Options} Options
 * @typedef {import('./types.js').Context} Context
 * @typedef {import('./types.js').Handle} Handle
 * @typedef {import('./types.js').Join} Join
 * @typedef {import('./types.js').Unsafe} Unsafe
 */

import {zwitch} from 'zwitch'
import {configure} from './configure.js'
import {handle} from './handle/index.js'
import {join} from './join.js'
import {unsafe} from './unsafe.js'

/**
 * @param {Node} tree
 * @param {Options} [options]
 * @returns {string}
 */
export function toMarkdown(tree, options = {}) {
  /** @type {Context} */
  // @ts-expect-error: we’ll add `handle` later.
  const context = {
    enter,
    stack: [],
    unsafe: [],
    join: [],
    handlers: {},
    options: {},
    indexStack: []
  }

  configure(context, {unsafe, join, handlers: handle})
  configure(context, options)

  if (context.options.tightDefinitions) {
    configure(context, {join: [joinDefinition]})
  }

  /** @type {Handle} */
  context.handle = zwitch('type', {
    invalid,
    // @ts-expect-error: hush.
    unknown,
    // @ts-expect-error: hush.
    handlers: context.handlers
  })

  let result = context.handle(tree, null, context, {
    before: '\n',
    after: '\n',
    now: {line: 1, column: 1},
    lineShift: 0
  })

  if (
    result &&
    result.charCodeAt(result.length - 1) !== 10 &&
    result.charCodeAt(result.length - 1) !== 13
  ) {
    result += '\n'
  }

  return result

  /** @type {Context['enter']} */
  function enter(name) {
    context.stack.push(name)
    return exit

    function exit() {
      context.stack.pop()
    }
  }
}

/**
 * @type {Handle}
 * @param {unknown} value
 */
function invalid(value) {
  throw new Error('Cannot handle value `' + value + '`, expected node')
}

/**
 * @type {Handle}
 * @param {Node} node
 */
function unknown(node) {
  throw new Error('Cannot handle unknown node `' + node.type + '`')
}

/** @type {Join} */
function joinDefinition(left, right) {
  // No blank line between adjacent definitions.
  if (left.type === 'definition' && left.type === right.type) {
    return 0
  }
}
