import { $dt, ThemeService } from '@openuxkit/styled';
import BaseComponent from '@openvue/core/basecomponent';
import ChartStyle from 'openvue/chart/style';
import { openBlock, createElementBlock, mergeProps, createElementVNode } from 'vue';

var script$1 = {
  name: 'BaseChart',
  "extends": BaseComponent,
  props: {
    type: String,
    data: null,
    options: null,
    plugins: null,
    width: {
      type: Number,
      "default": 300
    },
    height: {
      type: Number,
      "default": 150
    },
    canvasProps: {
      type: null,
      "default": null
    },
    themed: {
      type: Boolean,
      "default": true
    }
  },
  style: ChartStyle,
  provide: function provide() {
    return {
      $pcChart: this,
      $parentInstance: this
    };
  }
};

function _typeof$1(o) { "@babel/helpers - typeof"; return _typeof$1 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof$1(o); }
function ownKeys$1(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$1(Object(t), true).forEach(function (r) { _defineProperty$1(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$1(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty$1(e, r, t) { return (r = _toPropertyKey$1(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; }
function _toPropertyKey$1(t) { var i = _toPrimitive$1(t, "string"); return "symbol" == _typeof$1(i) ? i : i + ""; }
function _toPrimitive$1(t, r) { if ("object" != _typeof$1(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != _typeof$1(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = true, o = false; try { if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = true, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }

/**
 * Chart.js renders to a canvas, so it cannot consume `var(--p-*)` references the way the rest of
 * the library does. Token names are resolved through $dt so they stay correct across presets, then
 * read off the document to get concrete values the canvas can paint with.
 */

/*
 * Fixed categorical order. Slots are assigned by dataset index and never cycled or reordered, so a
 * series keeps its colour when other series are added or filtered out.
 *
 * Both rows were checked for the OKLCH lightness band, chroma floor, adjacent colour-vision
 * separation and contrast against their own surface. Dark is a separate selection rather than a
 * lightened copy of light, because simply brightening these hues pushes them out of the band.
 */
var SERIES_TOKENS = [{
  light: 'blue.500',
  dark: 'blue.500',
  fallback: '#3b82f6'
}, {
  light: 'amber.500',
  dark: 'amber.600',
  fallback: '#f59e0b'
}, {
  light: 'violet.500',
  dark: 'violet.500',
  fallback: '#8b5cf6'
}, {
  light: 'emerald.500',
  dark: 'emerald.600',
  fallback: '#10b981'
}, {
  light: 'fuchsia.500',
  dark: 'fuchsia.500',
  fallback: '#d946ef'
}, {
  light: 'rose.500',
  dark: 'rose.500',
  fallback: '#f43f5e'
}, {
  light: 'cyan.500',
  dark: 'cyan.600',
  fallback: '#06b6d4'
}, {
  light: 'lime.500',
  dark: 'lime.600',
  fallback: '#84cc16'
}];
var SEMANTIC_TOKENS = {
  text: 'text.color',
  textMuted: 'text.muted.color',
  border: 'content.border.color',
  background: 'content.background'
};
var resolve = function resolve(styles, token) {
  return styles.getPropertyValue($dt(token).name).trim();
};

/*
 * Derive the scheme from the resolved surface rather than from a class name or media query, so it
 * stays correct however the application happens to toggle dark mode.
 */
var isDarkSurface = function isDarkSurface(color) {
  var parsed = parseColor(color);
  if (!parsed) return false;
  var _parsed$map = parsed.map(function (channel) {
      var c = channel / 255;
      return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
    }),
    _parsed$map2 = _slicedToArray(_parsed$map, 3),
    r = _parsed$map2[0],
    g = _parsed$map2[1],
    b = _parsed$map2[2];
  return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.5;
};
var parseColor = function parseColor(value) {
  if (!value) return null;
  var hex = value.match(/^#([\da-f]{3}|[\da-f]{6})$/i);
  if (hex) {
    var digits = hex[1].length === 3 ? _toConsumableArray(hex[1]).map(function (d) {
      return d + d;
    }) : hex[1].match(/../g);
    return digits.map(function (d) {
      return parseInt(d, 16);
    });
  }
  var rgb = value.match(/rgba?\(([^)]+)\)/i);
  if (rgb) {
    return rgb[1].split(/[,\s/]+/).slice(0, 3).map(Number);
  }
  return null;
};
var withAlpha = function withAlpha(color, alpha) {
  var parsed = parseColor(color);
  return parsed ? "rgba(".concat(parsed[0], ", ").concat(parsed[1], ", ").concat(parsed[2], ", ").concat(alpha, ")") : color;
};

/**
 * Reads the active theme off the given element and returns the palette plus a Chart.js options
 * skeleton. Returns null when nothing resolves, which is the case in non-browser environments.
 */
function getChartTheme(element, type) {
  var datasetCount = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  if (typeof window === 'undefined' || !element) return null;
  try {
    var styles = getComputedStyle(element);
    var text = resolve(styles, SEMANTIC_TOKENS.text);
    if (!text) return null;
    var textMuted = resolve(styles, SEMANTIC_TOKENS.textMuted) || text;
    var border = resolve(styles, SEMANTIC_TOKENS.border);
    var background = resolve(styles, SEMANTIC_TOKENS.background);
    var scheme = isDarkSurface(background) ? 'dark' : 'light';
    var fontFamily = styles.fontFamily;

    /*
     * A slot that cannot be resolved keeps its position and falls back to the light step.
     * Dropping it instead would shift every later series onto the wrong colour.
     */
    var palette = SERIES_TOKENS.map(function (slot) {
      return resolve(styles, slot[scheme]) || resolve(styles, slot.light) || slot.fallback;
    });
    var options = {
      plugins: {
        /*
         * A legend is the secondary encoding that keeps series identifiable when a fill
         * does not clear contrast against the surface. Small point-style swatches keep it
         * from competing with the plot.
         */
        legend: {
          /*
           * With one series the title or surrounding copy already names it, so a legend
           * would repeat a single label. Types coloured per data point always keep it,
           * since there the legend carries the identity of each slice.
           */
          display: datasetCount > 1 || PER_POINT.includes(type),
          labels: {
            color: text,
            font: {
              family: fontFamily,
              size: 12
            },
            usePointStyle: true,
            pointStyle: 'circle',
            boxWidth: 8,
            boxHeight: 8,
            padding: 16
          }
        },
        tooltip: {
          titleColor: text,
          bodyColor: textMuted,
          backgroundColor: background,
          borderColor: border,
          borderWidth: 1,
          titleFont: {
            family: fontFamily,
            size: 12,
            weight: 600
          },
          bodyFont: {
            family: fontFamily,
            size: 12
          },
          padding: 12,
          cornerRadius: 6,
          boxPadding: 6,
          usePointStyle: true
        }
      }
    };
    var scales = getScales(type, {
      textMuted: textMuted,
      border: border,
      fontFamily: fontFamily
    });
    if (scales) options.scales = scales;
    return {
      scheme: scheme,
      palette: palette,
      text: text,
      textMuted: textMuted,
      border: border,
      options: options
    };
  } catch (_unused) {
    /*
     * Theming is an enhancement, so a token that cannot be resolved must never stop the
     * chart from rendering. Fall back to Chart.js defaults instead.
     */
    return null;
  }
}

/*
 * Scale shape is per chart type. Radial types carry a single `r` scale and the circular types
 * have no scales at all, so a cartesian x/y pair would draw axes that do not belong there.
 */
var CARTESIAN = ['bar', 'line', 'scatter', 'bubble'];
var RADIAL = ['radar', 'polarArea'];
var PER_POINT = ['pie', 'doughnut', 'polarArea'];
function getScales(type, _ref) {
  var textMuted = _ref.textMuted,
    border = _ref.border,
    fontFamily = _ref.fontFamily;
  var ticks = {
    color: textMuted,
    font: {
      family: fontFamily,
      size: 12
    },
    padding: 8
  };
  if (CARTESIAN.includes(type)) {
    /*
     * Grid and axes stay recessive so the data reads first. Only the value axis keeps its
     * lines: a grid in both directions boxes the plot in without adding information.
     */
    return {
      x: {
        ticks: ticks,
        grid: {
          display: false
        },
        border: {
          display: false
        }
      },
      y: {
        ticks: ticks,
        grid: {
          color: border,
          drawTicks: false
        },
        border: {
          display: false
        }
      }
    };
  }
  if (RADIAL.includes(type)) {
    return {
      r: {
        ticks: _objectSpread$1(_objectSpread$1({}, ticks), {}, {
          backdropColor: 'transparent',
          showLabelBackdrop: false
        }),
        grid: {
          color: border
        },
        angleLines: {
          color: border
        },
        pointLabels: {
          color: textMuted,
          font: {
            family: fontFamily,
            size: 12
          }
        }
      }
    };
  }
  return null;
}

/**
 * Applies palette slots to any dataset that has not set its own colours. Radial types read their
 * colours per data point rather than per dataset, so they receive an array instead of a scalar.
 */
function applyPalette(data, type, palette) {
  if (!data || !Array.isArray(data.datasets) || !palette.length) return data;
  var perPoint = PER_POINT.includes(type);
  return _objectSpread$1(_objectSpread$1({}, data), {}, {
    datasets: data.datasets.map(function (dataset, index) {
      var next = _objectSpread$1({}, dataset);
      var color = palette[index % palette.length];
      var set = function set(key, value) {
        if (next[key] === undefined) next[key] = value;
      };
      if (perPoint) {
        set('backgroundColor', (dataset.data || []).map(function (_, i) {
          return palette[i % palette.length];
        }));
        /*
         * A ring in the surface colour separates adjacent slices without a visible stroke.
         */
        set('borderColor', 'transparent');
        set('borderWidth', 2);
        return next;
      }
      var filled = dataset.fill || type === 'radar';
      set('backgroundColor', filled ? withAlpha(color, 0.2) : color);
      set('borderColor', color);
      if (type === 'bar') {
        // rounded at the data end, flat where the bar meets the baseline
        set('borderRadius', 4);
        set('borderSkipped', 'start');
        set('maxBarThickness', 48);
        return next;
      }
      if (type === 'bubble') {
        /*
         * Bubble radius comes from the r value of each point, so it must not be overridden.
         */
        set('backgroundColor', withAlpha(color, 0.6));
        set('borderWidth', 1);
        return next;
      }
      if (type === 'scatter') {
        /*
         * The points are the whole chart here, so unlike a line they stay visible.
         */
        set('pointRadius', 5);
        set('pointHoverRadius', 7);
        set('pointBackgroundColor', color);
        set('pointBorderColor', color);
        return next;
      }
      set('borderWidth', 2);
      /*
       * On a line the points stay hidden until hover so a dense series reads as a line, but
       * the hit area is kept well above the mark size so they remain easy to target.
       */
      set('pointRadius', 0);
      set('pointHoverRadius', 4);
      set('pointHitRadius', 12);
      set('pointBackgroundColor', color);
      set('pointBorderColor', color);
      return next;
    })
  });
}

/**
 * Merges token defaults underneath user supplied options. Anything the user sets wins, including
 * a value nested several levels down, so existing configurations keep working unchanged.
 */
function mergeOptions(defaults, options) {
  if (!options) return defaults;
  if (!defaults) return options;
  var merged = _objectSpread$1({}, defaults);
  for (var _i = 0, _Object$keys = Object.keys(options); _i < _Object$keys.length; _i++) {
    var key = _Object$keys[_i];
    var value = options[key];
    var base = defaults[key];
    var mergeable = value && base && _typeof$1(value) === 'object' && _typeof$1(base) === 'object' && !Array.isArray(value) && !Array.isArray(base);
    merged[key] = mergeable ? mergeOptions(base, value) : value;
  }
  return merged;
}

var script = {
  name: 'Chart',
  "extends": script$1,
  inheritAttrs: false,
  emits: ['select', 'loaded'],
  chart: null,
  isDestroyed: false,
  themeChangeListener: null,
  themeObserver: null,
  watch: {
    /*
     * Use deep watch to enable triggering watch for changes within structure
     * otherwise the entire data object needs to be replaced to trigger watch
     */
    data: {
      handler: function handler() {
        this.syncChart();
      },
      deep: true
    },
    type: function type() {
      this.reinit();
    },
    options: {
      handler: function handler() {
        this.syncChart();
      },
      deep: true
    },
    themed: function themed() {
      this.unbindThemeListeners();
      this.bindThemeListeners();
      this.syncChart();
    }
  },
  mounted: function mounted() {
    this.initChart();
    this.bindThemeListeners();
  },
  beforeUnmount: function beforeUnmount() {
    this.isDestroyed = true;
    this.unbindThemeListeners();
    this.destroyChart();
  },
  methods: {
    initChart: function initChart() {
      var _this = this;
      import('chart.js/auto').then(function (module) {
        _this.destroyChart();

        /*
         * The import is async, so the component may already be unmounted by the time it resolves.
         * Creating the chart here would leave an instance attached to a detached canvas.
         */
        if (_this.isDestroyed) {
          return;
        }
        if (module && module["default"]) {
          var _this$resolveConfig = _this.resolveConfig(),
            data = _this$resolveConfig.data,
            options = _this$resolveConfig.options;
          _this.chart = new module["default"](_this.$refs.canvas, {
            type: _this.type,
            data: data,
            options: options,
            plugins: _this.plugins
          });
        }
        _this.$emit('loaded', _this.chart);
      })["catch"](function (error) {
        console.error('[OpenVue] Chart requires chart.js to be installed. Run "npm install chart.js" to use this component.', error);
      });
    },
    resolveConfig: function resolveConfig() {
      var _this$data$datasets$l, _this$data;
      var theme = this.themed ? getChartTheme(this.$el, this.type, (_this$data$datasets$l = (_this$data = this.data) === null || _this$data === void 0 || (_this$data = _this$data.datasets) === null || _this$data === void 0 ? void 0 : _this$data.length) !== null && _this$data$datasets$l !== void 0 ? _this$data$datasets$l : 0) : null;
      return {
        data: theme ? applyPalette(this.data, this.type, theme.palette) : this.data,
        options: theme ? mergeOptions(theme.options, this.options) : this.options
      };
    },
    syncChart: function syncChart() {
      if (!this.chart) {
        return;
      }
      var _this$resolveConfig2 = this.resolveConfig(),
        data = _this$resolveConfig2.data,
        options = _this$resolveConfig2.options;

      /*
       * Chart.js keeps a reference to the config passed on construction, so replaced data and
       * options objects have to be reassigned before update() can pick the new values up.
       */
      this.chart.data = data;
      this.chart.options = options;
      this.chart.update();
    },
    bindThemeListeners: function bindThemeListeners() {
      var _this2 = this;
      if (!this.themed || typeof window === 'undefined') {
        return;
      }
      this.themeChangeListener = function () {
        return _this2.syncChart();
      };
      ThemeService.on('theme:change', this.themeChangeListener);

      /*
       * A preset swap emits through ThemeService, but toggling dark mode only rewrites CSS
       * custom properties, which fires nothing. Watching the attributes that scope those
       * properties covers the toggle however the application implements it.
       */
      if (typeof MutationObserver !== 'undefined') {
        this.themeObserver = new MutationObserver(this.themeChangeListener);
        this.themeObserver.observe(document.documentElement, {
          attributes: true,
          attributeFilter: ['class', 'data-theme']
        });
      }
    },
    unbindThemeListeners: function unbindThemeListeners() {
      if (this.themeChangeListener) {
        ThemeService.off('theme:change', this.themeChangeListener);
        this.themeChangeListener = null;
      }
      if (this.themeObserver) {
        this.themeObserver.disconnect();
        this.themeObserver = null;
      }
    },
    destroyChart: function destroyChart() {
      if (this.chart) {
        this.chart.destroy();
        this.chart = null;
      }
    },
    getCanvas: function getCanvas() {
      return this.$refs.canvas;
    },
    getChart: function getChart() {
      return this.chart;
    },
    getBase64Image: function getBase64Image() {
      return this.chart.toBase64Image();
    },
    refresh: function refresh() {
      if (this.chart) {
        this.chart.update();
      }
    },
    reinit: function reinit() {
      this.initChart();
    },
    onCanvasClick: function onCanvasClick(event) {
      if (this.chart) {
        var element = this.chart.getElementsAtEventForMode(event, 'nearest', {
          intersect: true
        }, false);
        var dataset = this.chart.getElementsAtEventForMode(event, 'dataset', {
          intersect: true
        }, false);
        if (element && element[0] && dataset) {
          this.$emit('select', {
            originalEvent: event,
            element: element[0],
            dataset: dataset
          });
        }
      }
    }
  }
};

function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var _hoisted_1 = ["width", "height"];
function render(_ctx, _cache, $props, $setup, $data, $options) {
  return openBlock(), createElementBlock("div", mergeProps({
    "class": _ctx.cx('root'),
    style: _ctx.sx('root')
  }, _ctx.ptmi('root')), [createElementVNode("canvas", mergeProps({
    ref: "canvas",
    width: _ctx.width,
    height: _ctx.height,
    onClick: _cache[0] || (_cache[0] = function ($event) {
      return $options.onCanvasClick($event);
    })
  }, _objectSpread(_objectSpread({}, _ctx.canvasProps), _ctx.ptm('canvas'))), null, 16, _hoisted_1)], 16);
}

script.render = render;

export { script as default };
//# sourceMappingURL=index.mjs.map
