"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _postcss = _interopRequireDefault(require("postcss"));
var _parse = _interopRequireDefault(require("./postcss/import/parse"));
var _meta = _interopRequireDefault(require("./postcss/meta"));
var _nesting = _interopRequireDefault(require("./postcss/nesting"));
var _increasing_specificity = require("./postcss/root/increasing_specificity");
var _replace = _interopRequireDefault(require("./postcss/root/replace"));
var _section_size = _interopRequireDefault(require("./postcss/section_size"));
var _symbol = _interopRequireDefault(require("./theme/symbol"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const absoluteUnits = {
  cm: v => v * 960 / 25.4,
  in: v => v * 96,
  mm: v => v * 96 / 25.4,
  pc: v => v * 16,
  pt: v => v * 4 / 3,
  px: v => v,
  q: v => v * 24 / 25.4
};
const convertToPixel = value => {
  if (typeof value !== 'string') return undefined;
  const matched = value.match(/^(-?[.0-9]+)([a-z]+)$/i);
  if (!matched) return undefined;
  const [, num, unit] = matched;
  const parsed = Number.parseFloat(num);
  if (Number.isNaN(parsed)) return undefined;
  const conv = absoluteUnits[unit.toLowerCase()];
  return conv ? conv(parsed) : undefined;
};
const memoizeProp = name => `${name}Memoized`;
const reservedMetaType = {
  theme: String
};

/**
 * Marpit theme class.
 */
class Theme {
  /**
   * Create a Theme instance.
   *
   * You should use {@link Theme.fromCSS} unless there is some particular
   * reason.
   *
   * @param {string} name The name of theme.
   * @param {string} css The content of CSS.
   * @hideconstructor
   */
  constructor(name, css) {
    /**
     * The name of theme.
     * @type {string}
     */
    this.name = name;

    /**
     * The content of theme CSS.
     * @type {string}
     */
    this.css = css;

    /**
     * Parsed metadata from CSS comments.
     * @type {Object}
     */
    this.meta = Object.freeze({});

    /**
     * Parsed `@import` rules.
     * @type {module:postcss/import/parse~ImportMeta[]}
     */
    this.importRules = [];

    /**
     * Slide width. It requires the absolute unit supported in CSS.
     * @type {string}
     */
    this.width = undefined;

    /**
     * Slide height. It requires the absolute unit supported in CSS.
     * @type {string}
     */
    this.height = undefined;
    this.memoizeInit('width');
    this.memoizeInit('height');
  }

  /**
   * Create a Theme instance from Marpit theme CSS.
   *
   * @alias Theme.fromCSS
   * @param {string} cssString The string of Marpit theme CSS. It requires
   *     `@theme` meta comment.
   * @param {Object} [opts]
   * @param {Object} [opts.metaType] An object for defined types for metadata.
   * @param {Object} [opts.cssNesting] Enable support for CSS nesting.
   */
  static fromCSS(cssString, opts = {}) {
    const metaType = {
      ...(opts.metaType || {}),
      ...reservedMetaType
    };
    const {
      css,
      result
    } = (0, _postcss.default)([!!opts.cssNesting && _nesting.default, (0, _meta.default)({
      metaType
    }), (0, _replace.default)({
      pseudoClass: _increasing_specificity.pseudoClass
    }), (0, _section_size.default)({
      preferedPseudoClass: _increasing_specificity.pseudoClass
    }), _parse.default].filter(p => p)).process(cssString);
    if (!opts[_symbol.default] && !result.marpitMeta.theme) throw new Error('Marpit theme CSS requires @theme meta.');
    const theme = new Theme(result.marpitMeta.theme, css);
    theme.importRules = [...result.marpitImport];
    theme.meta = Object.freeze({
      ...result.marpitMeta
    });
    Object.assign(theme, {
      ...result.marpitSectionSize
    });
    return Object.freeze(theme);
  }

  /**
   * The converted width into pixel.
   *
   * @alias Theme#widthPixel
   * @type {number}
   * @readonly
   */
  get widthPixel() {
    return this.memoize('width', convertToPixel);
  }

  /**
   * The converted height into pixel.
   *
   * @alias Theme#heightPixel
   * @type {number}
   * @readonly
   */
  get heightPixel() {
    return this.memoize('height', convertToPixel);
  }

  /** @private */
  memoize(prop, func) {
    if (this[memoizeProp(prop)].has(this[prop])) return this[memoizeProp(prop)].get(this[prop]);
    const converted = func(this[prop]);
    this[memoizeProp(prop)].set(this[prop], converted);
    return converted;
  }

  /** @private */
  memoizeInit(prop) {
    if (!this[memoizeProp(prop)]) Object.defineProperty(this, memoizeProp(prop), {
      value: new Map()
    });
  }
}
var _default = exports.default = Theme;