/**
 * Formatea una fecha en formato legible en español, con opción a incluir hora, minutos y día de la semana.
 *
 * @param {string} fechaStr - Cadena de texto representando una fecha válida (ISO 8601 o similar).
 * @param {Object} [config] - Configuración opcional para mostrar hora, minutos y día de la semana.
 * @param {boolean} [config.horas=false] - Indica si se debe incluir la hora.
 * @param {boolean} [config.minutos=false] - Indica si se deben incluir los minutos (requiere config.horas).
 * @param {boolean} [config.diaSemana=false] - Indica si se debe incluir el día de la semana.
 * @returns {string} Fecha formateada en español.
 * @throws {Error} Lanza un error si la fecha proporcionada no es válida.
 *
 * @example
 * formatFecha('2025-03-25T14:30:00Z');
 * // Retorna: "25 de marzo del 2025"
 *
 * @example
 * formatFecha('2025-03-25T14:30:00Z', { horas: true });
 * // Retorna: "25 de marzo del 2025 - 14"
 *
 * @example
 * formatFecha('2025-03-25T14:30:00Z', { horas: true, minutos: true });
 * // Retorna: "25 de marzo del 2025 - 14:30"
 *
 * @example
 * formatFecha('2025-03-25T14:30:00Z', { diaSemana: true });
 * // Retorna: "martes, 25 de marzo del 2025"
 */
export function formatFecha(
  fechaStr: string,
  config: { horas?: boolean; minutos?: boolean; diaSemana?: boolean } = {
    horas: false,
    minutos: false,
    diaSemana: false,
  }
): string {
  const fecha = new Date(fechaStr);

  if (isNaN(fecha.getTime())) {
    throw new Error(`Fecha inválida: ${fechaStr}`);
  }

  const diasSemana = [
    'domingo',
    'lunes',
    'martes',
    'miércoles',
    'jueves',
    'viernes',
    'sábado',
  ];
  const meses = [
    'enero',
    'febrero',
    'marzo',
    'abril',
    'mayo',
    'junio',
    'julio',
    'agosto',
    'septiembre',
    'octubre',
    'noviembre',
    'diciembre',
  ];

  const dia = fecha.getUTCDate();
  const mes = meses[fecha.getUTCMonth()];
  const año = fecha.getUTCFullYear();
  const hora = (fecha.getUTCHours() - 6 + 24) % 24;
  const minutos = fecha.getUTCMinutes().toString().padStart(2, '0');
  const diaSemana = diasSemana[fecha.getUTCDay()];

  let fechaFormateada = '';

  if (config.diaSemana) {
    fechaFormateada += `${diaSemana}, `;
  }

  fechaFormateada += `${dia} de ${mes} del ${año}`;

  if (config.horas) {
    fechaFormateada += ` - ${hora}`;
    if (config.minutos) {
      fechaFormateada += `:${minutos}`;
    }
  }

  return fechaFormateada;
}

export function formatearFecha(fecha: string, tipo: 'inicio' | 'fin'): string {
  const sufijo = tipo === 'inicio' ? 'T00:00:01' : 'T23:59:59';
  return `${fecha}${sufijo}`;
}

export function formatDateLocal(date: Date): string {
  const pad = (n: number) => n.toString().padStart(2, '0');
  const yyyy = date.getFullYear();
  const MM = pad(date.getMonth() + 1);
  const dd = pad(date.getDate());
  const hh = pad(date.getHours());
  const mm = pad(date.getMinutes());

  return `${yyyy}-${MM}-${dd}T${hh}:${mm}`;
}

/**
 * Formatea un rango de fechas en español, optimizando la salida según si coinciden año, mes y día.
 *
 * @param {string} fechaInicio - Fecha de inicio en formato ISO 8601 (ej: '2025-08-13T18:44:00.000Z').
 * @param {string} fechaFin - Fecha de fin en formato ISO 8601 (ej: '2025-08-15T18:44:00.000Z').
 * @returns {string} Rango de fechas formateado en español.
 * @throws {Error} Lanza un error si alguna de las fechas proporcionadas no es válida.
 *
 * @example
 * formatearRangoFechas('2025-08-13T12:00:00.000Z', '2025-08-13T18:00:00.000Z');
 * // Retorna: "el 13 de agosto del 2025 de 12:00 a 18:00"
 *
 * @example
 * formatearRangoFechas('2025-08-13T18:44:00.000Z', '2025-08-15T18:44:00.000Z');
 * // Retorna: "del 13 al 15 de agosto del 2025"
 *
 * @example
 * formatearRangoFechas('2025-08-13T18:44:00.000Z', '2025-09-13T18:44:00.000Z');
 * // Retorna: "del 13 de agosto al 13 de septiembre del 2025"
 *
 * @example
 * formatearRangoFechas('2025-08-13T18:44:00.000Z', '2026-01-13T18:44:00.000Z');
 * // Retorna: "del 13 de agosto del 2025 al 13 de enero del 2026"
 */
export function formatearRangoFechas(
  fechaInicio: string,
  fechaFin: string
): string {
  const inicio = new Date(fechaInicio);
  const fin = new Date(fechaFin);

  if (isNaN(inicio.getTime())) {
    throw new Error(`Fecha de inicio inválida: ${fechaInicio}`);
  }

  if (isNaN(fin.getTime())) {
    throw new Error(`Fecha de fin inválida: ${fechaFin}`);
  }

  const meses = [
    'enero',
    'febrero',
    'marzo',
    'abril',
    'mayo',
    'junio',
    'julio',
    'agosto',
    'septiembre',
    'octubre',
    'noviembre',
    'diciembre',
  ];

  const diaInicio = inicio.getUTCDate();
  const mesInicio = meses[inicio.getUTCMonth()];
  const añoInicio = inicio.getUTCFullYear();

  const diaFin = fin.getUTCDate();
  const mesFin = meses[fin.getUTCMonth()];
  const añoFin = fin.getUTCFullYear();

  // Si es el mismo día (año, mes y día iguales)
  if (
    añoInicio === añoFin &&
    inicio.getUTCMonth() === fin.getUTCMonth() &&
    diaInicio === diaFin
  ) {
    const horaInicio = (inicio.getUTCHours() - 6 + 24) % 24;
    const minutosInicio = inicio.getUTCMinutes().toString().padStart(2, '0');
    const horaFin = (fin.getUTCHours() - 6 + 24) % 24;
    const minutosFin = fin.getUTCMinutes().toString().padStart(2, '0');

    return `el ${diaInicio} de ${mesInicio} del ${añoInicio} de ${horaInicio}:${minutosInicio} a ${horaFin}:${minutosFin}`;
  }

  // Si el año y mes son iguales pero días diferentes
  if (añoInicio === añoFin && inicio.getUTCMonth() === fin.getUTCMonth()) {
    return `del ${diaInicio} al ${diaFin} de ${mesInicio} del ${añoInicio}`;
  }

  // Si el año es igual pero el mes es diferente
  if (añoInicio === añoFin) {
    return `del ${diaInicio} de ${mesInicio} al ${diaFin} de ${mesFin} del ${añoInicio}`;
  }

  // Si tanto el año como el mes son diferentes
  return `del ${diaInicio} de ${mesInicio} del ${añoInicio} al ${diaFin} de ${mesFin} del ${añoFin}`;
}

/**
 * Formatea fechas para mostrar en las cartas de eventos, optimizando el espacio disponible.
 * Retorna un objeto con el texto del mes y el texto del día para mostrar en la interfaz.
 *
 * @param {Date | string} fechaInicio - Fecha de inicio del evento.
 * @param {Date | string} fechaFin - Fecha de fin del evento.
 * @returns {{ mesTexto: string, diaTexto: string }} Objeto con texto del mes y día formateados.
 * @throws {Error} Lanza un error si alguna de las fechas proporcionadas no es válida.
 *
 * @example
 * formatearFechaCard(new Date('2025-08-15'), new Date('2025-08-15'));
 * // Retorna: { mesTexto: 'AGO', diaTexto: '15' }
 *
 * @example
 * formatearFechaCard(new Date('2025-08-15'), new Date('2025-08-20'));
 * // Retorna: { mesTexto: 'AGO', diaTexto: '15 - 20' }
 *
 * @example
 * formatearFechaCard(new Date('2025-08-28'), new Date('2025-09-05'));
 * // Retorna: { mesTexto: 'AGO - SEP', diaTexto: '28 - 5' }
 */
export function formatearFechaCard(
  fechaInicio: Date | string,
  fechaFin: Date | string
): { mesTexto: string; diaTexto: string } {
  // Convertir a Date si son strings
  const inicio =
    typeof fechaInicio === 'string' ? new Date(fechaInicio) : fechaInicio;
  const fin = typeof fechaFin === 'string' ? new Date(fechaFin) : fechaFin;

  if (isNaN(inicio.getTime())) {
    throw new Error(`Fecha de inicio inválida: ${fechaInicio}`);
  }

  if (isNaN(fin.getTime())) {
    throw new Error(`Fecha de fin inválida: ${fechaFin}`);
  }

  const mesInicioCorto = inicio
    .toLocaleDateString('es-ES', { month: 'short' })
    .toUpperCase();
  const mesFinCorto = fin
    .toLocaleDateString('es-ES', { month: 'short' })
    .toUpperCase();

  const diaInicio = inicio.getDate();
  const diaFin = fin.getDate();

  // Si es el mismo día
  const esElMismoDia = inicio.toDateString() === fin.toDateString();

  // Si es el mismo mes pero diferentes días
  const esElMismoMes =
    inicio.getMonth() === fin.getMonth() &&
    inicio.getFullYear() === fin.getFullYear();

  if (esElMismoDia) {
    return {
      mesTexto: mesInicioCorto,
      diaTexto: diaInicio.toString(),
    };
  } else if (esElMismoMes) {
    return {
      mesTexto: mesInicioCorto,
      diaTexto: `${diaInicio} - ${diaFin}`,
    };
  } else {
    // Diferentes meses
    return {
      mesTexto: `${mesInicioCorto} - ${mesFinCorto}`,
      diaTexto: `${diaInicio} - ${diaFin}`,
    };
  }
}

/**
 * Extrae y formatea el horario entre dos fechas.
 * Retorna el rango de horas en formato "HH:MM a HH:MM Hrs".
 *
 * @param {string | Date} fechaInicio - Fecha de inicio en formato ISO 8601 o objeto Date.
 * @param {string | Date} fechaFin - Fecha de fin en formato ISO 8601 o objeto Date.
 * @returns {string} Horario formateado en formato "HH:MM a HH:MM Hrs".
 * @throws {Error} Lanza un error si alguna de las fechas proporcionadas no es válida.
 *
 * @example
 * formatearHorario('2025-08-13T13:00:00.000Z', '2025-08-13T14:00:00.000Z');
 * // Retorna: "07:00 a 08:00 Hrs" (ajustado a UTC-6)
 *
 * @example
 * formatearHorario(new Date('2025-08-13T19:30:00.000Z'), new Date('2025-08-13T21:45:00.000Z'));
 * // Retorna: "13:30 a 15:45 Hrs"
 *
 * @example
 * formatearHorario('2025-08-13T13:00:00.000Z', '2025-08-13T13:00:00.000Z');
 * // Retorna: "07:00 Hrs" (misma hora)
 */
export function formatearHorario(
  fechaInicio: string | Date,
  fechaFin: string | Date
): string {
  // Convertir a Date si son strings
  const inicio =
    typeof fechaInicio === 'string' ? new Date(fechaInicio) : fechaInicio;
  const fin = typeof fechaFin === 'string' ? new Date(fechaFin) : fechaFin;

  if (isNaN(inicio.getTime())) {
    throw new Error(`Fecha de inicio inválida: ${fechaInicio}`);
  }

  if (isNaN(fin.getTime())) {
    throw new Error(`Fecha de fin inválida: ${fechaFin}`);
  }

  // Ajustar a zona horaria UTC-6 (México)
  const horaInicio = (inicio.getUTCHours() - 6 + 24) % 24;
  const minutosInicio = inicio.getUTCMinutes().toString().padStart(2, '0');

  const horaFin = (fin.getUTCHours() - 6 + 24) % 24;
  const minutosFin = fin.getUTCMinutes().toString().padStart(2, '0');

  const horaInicioFormateada = `${horaInicio
    .toString()
    .padStart(2, '0')}:${minutosInicio}`;
  const horaFinFormateada = `${horaFin
    .toString()
    .padStart(2, '0')}:${minutosFin}`;

  // Si es la misma hora, solo mostrar una vez
  if (horaInicioFormateada === horaFinFormateada) {
    return `${horaInicioFormateada} Hrs`;
  }

  return `${horaInicioFormateada} a ${horaFinFormateada}`;
}
