/**
 * D3 Time Format Polyfill - Lightweight date formatting and parsing.
 *
 * This is a drop-in replacement for d3-time-format, implementing only the methods
 * used in the Vega chart rendering system: timeFormat and timeParse.
 *
 * @see VD-5022 - [Zero Dependencies] remove d3 dependency
 */
/**
 * Mapping of format directives (e.g. %Y, %m) to their format functions.
 * Each function takes a Date and returns the formatted string segment.
 */
/* eslint-disable jsdoc/require-jsdoc */
const FORMAT_DIRECTIVES = {
  '%Y': (d) => String(d.getFullYear()),
  '%m': (d) => String(d.getMonth() + 1).padStart(2, '0'),
  '%d': (d) => String(d.getDate()).padStart(2, '0'),
  '%H': (d) => String(d.getHours()).padStart(2, '0'),
  '%I': (d) => {
    const hours = d.getHours() % 12;
    return String(hours === 0 ? 12 : hours).padStart(2, '0');
  },
  '%M': (d) => String(d.getMinutes()).padStart(2, '0'),
  '%S': (d) => String(d.getSeconds()).padStart(2, '0'),
  '%L': (d) => String(d.getMilliseconds()).padStart(3, '0'),
  '%b': (d) => {
    const months = [
      'Jan',
      'Feb',
      'Mar',
      'Apr',
      'May',
      'Jun',
      'Jul',
      'Aug',
      'Sep',
      'Oct',
      'Nov',
      'Dec',
    ];
    return months[d.getMonth()];
  },
  '%B': (d) => {
    const months = [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
      'July',
      'August',
      'September',
      'October',
      'November',
      'December',
    ];
    return months[d.getMonth()];
  },
  '%a': (d) => {
    const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    return days[d.getDay()];
  },
  '%p': (d) => (d.getHours() >= 12 ? 'PM' : 'AM'),
  '%%': () => '%',
};
/* eslint-enable jsdoc/require-jsdoc */
/**
 * Creates a date formatter function for the given specifier.
 * Mimics d3.timeFormat().
 *
 * Supported directives: %Y, %m, %d, %H, %I, %M, %S, %L, %b, %B, %a, %p, %%
 *
 * @param {string} specifier - Format specifier string (e.g., '%Y-%m-%d', '.%L', ':%S').
 * @returns {Function} A function that formats a Date into a string.
 */
export function timeFormat(specifier) {
  return (date) => {
    let result = specifier;
    // Sort directives by length (longest first) to avoid partial matches
    const directives = Object.keys(FORMAT_DIRECTIVES).sort((a, b) => b.length - a.length);
    for (const directive of directives) {
      if (result.includes(directive)) {
        result = result.split(directive).join(FORMAT_DIRECTIVES[directive](date));
      }
    }
    return result;
  };
}
/**
 * Mapping of format directives to regex capture patterns for parsing.
 */
/* eslint-disable jsdoc/require-jsdoc */
const PARSE_PATTERNS = {
  /** Parses a 4-digit year. */
  '%Y': {
    regex: '(\\d{4})',
    handler: (m, p) => {
      p.year = parseInt(m, 10);
    },
  },
  /** Parses a 2-digit month. */
  '%m': {
    regex: '(\\d{2})',
    handler: (m, p) => {
      p.month = parseInt(m, 10) - 1;
    },
  },
  /** Parses a 2-digit day. */
  '%d': {
    regex: '(\\d{2})',
    handler: (m, p) => {
      p.day = parseInt(m, 10);
    },
  },
  /** Parses a 2-digit hour (24h). */
  '%H': {
    regex: '(\\d{2})',
    handler: (m, p) => {
      p.hours = parseInt(m, 10);
    },
  },
  /** Parses a 2-digit hour (12h). */
  '%I': {
    regex: '(\\d{2})',
    handler: (m, p) => {
      p.hours = parseInt(m, 10);
    },
  },
  /** Parses a 2-digit minute. */
  '%M': {
    regex: '(\\d{2})',
    handler: (m, p) => {
      p.minutes = parseInt(m, 10);
    },
  },
  /** Parses a 2-digit second. */
  '%S': {
    regex: '(\\d{2})',
    handler: (m, p) => {
      p.seconds = parseInt(m, 10);
    },
  },
  /** Parses a 3-digit millisecond. */
  '%L': {
    regex: '(\\d{3})',
    handler: (m, p) => {
      p.milliseconds = parseInt(m, 10);
    },
  },
};
/**
 * Creates a date parser function for the given specifier.
 * Mimics d3.timeParse().
 *
 * Supported directives: %Y, %m, %d, %H, %I, %M, %S, %L
 *
 * @param {string} specifier - Parse specifier string (e.g., '%Y-%m-%d').
 * @returns {Function} A function that parses a string into a Date, or null on failure.
 */
// eslint-disable-next-line spellcheck/spell-checker
// eslint-disable-next-line @heartlandone/vega-sandbox-pr-2912-4fdb7f7e13850e8f188221df5d487aacee5df9e2-eslint/vega-mandatory-method-return-corresponds-to-comments-tag-verify
export function timeParse(specifier) {
  // Build regex and capture order from specifier
  const captureOrder = [];
  let regexStr = '^';
  let remaining = specifier;
  while (remaining.length > 0) {
    let matched = false;
    // Sort by key length (longest first) to avoid partial matches
    const keys = Object.keys(PARSE_PATTERNS).sort((a, b) => b.length - a.length);
    for (const key of keys) {
      if (remaining.startsWith(key)) {
        regexStr += PARSE_PATTERNS[key].regex;
        captureOrder.push({ handler: PARSE_PATTERNS[key].handler });
        remaining = remaining.slice(key.length);
        matched = true;
        break;
      }
    }
    if (!matched) {
      // Escape special regex chars for literal characters
      regexStr += remaining[0].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
      remaining = remaining.slice(1);
    }
  }
  regexStr += '$';
  const regex = new RegExp(regexStr);
  return (dateString) => {
    const match = dateString.match(regex);
    if (!match)
      return null;
    const parts = {
      year: 1970,
      month: 0,
      day: 1,
      hours: 0,
      minutes: 0,
      seconds: 0,
      milliseconds: 0,
    };
    for (let i = 0; i < captureOrder.length; i++) {
      captureOrder[i].handler(match[i + 1], parts);
    }
    return new Date(parts.year, parts.month, parts.day, parts.hours, parts.minutes, parts.seconds, parts.milliseconds);
  };
}
