import { displayTimeConfig, type DisplayTimeConfig } from "./runtime-config";

const inputPartsFormatters = new Map<string, Intl.DateTimeFormat>();
const offsetPartsFormatters = new Map<string, Intl.DateTimeFormat>();

export function displayTimeZone(): string {
  return displayTimeConfig().timeZone;
}

export function displayTimeLabel(): string {
  return displayTimeConfig().label;
}

function coerceDate(value: any): Date | null {
  if (value === null || value === undefined || value === "") return null;
  const date = value instanceof Date ? value : new Date(value);
  return Number.isNaN(date.getTime()) ? null : date;
}

function formatterKey(config: DisplayTimeConfig): string {
  return `${config.locale}|${config.timeZone}`;
}

function inputPartsFormatter(config: DisplayTimeConfig): Intl.DateTimeFormat {
  const key = formatterKey(config);
  const existing = inputPartsFormatters.get(key);
  if (existing) return existing;
  const formatter = new Intl.DateTimeFormat("en-CA", {
    timeZone: config.timeZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    hourCycle: "h23",
  });
  inputPartsFormatters.set(key, formatter);
  return formatter;
}

function offsetPartsFormatter(config: DisplayTimeConfig): Intl.DateTimeFormat {
  const key = formatterKey(config);
  const existing = offsetPartsFormatters.get(key);
  if (existing) return existing;
  const formatter = new Intl.DateTimeFormat("en-CA", {
    timeZone: config.timeZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
    hourCycle: "h23",
  });
  offsetPartsFormatters.set(key, formatter);
  return formatter;
}

function partsFor(value: any, formatter: Intl.DateTimeFormat): Record<string, string> | null {
  const date = coerceDate(value);
  if (!date) return null;
  return formatter.formatToParts(date).reduce((parts: Record<string, string>, part) => {
    if (part.type !== "literal") parts[part.type] = part.value;
    return parts;
  }, {});
}

export function fmtDate(value: any): string {
  const date = coerceDate(value);
  const config = displayTimeConfig();
  return date ? date.toLocaleString(config.locale, { timeZone: config.timeZone, hour12: false }) : "--";
}

export function fmtClock(value: any): string {
  const date = coerceDate(value);
  const config = displayTimeConfig();
  return date ? date.toLocaleTimeString(config.locale, { timeZone: config.timeZone, hour12: false }) : "--";
}

export function fmtDateTimeLocalInput(value: any): string {
  const config = displayTimeConfig();
  const parts = partsFor(value, inputPartsFormatter(config));
  if (!parts) return "";
  const hour = parts.hour === "24" ? "00" : parts.hour;
  return `${parts.year}-${parts.month}-${parts.day}T${hour}:${parts.minute}`;
}

export function displayDateStamp(value: any = new Date()): string {
  const config = displayTimeConfig();
  const parts = partsFor(value, inputPartsFormatter(config));
  if (!parts) return "";
  return `${parts.year}-${parts.month}-${parts.day}`;
}

function partsUtcMs(parts: Record<string, string>): number {
  const hour = parts.hour === "24" ? "00" : parts.hour;
  return Date.UTC(
    Number(parts.year),
    Number(parts.month) - 1,
    Number(parts.day),
    Number(hour),
    Number(parts.minute),
    Number(parts.second || "00"),
  );
}

function timeZoneOffsetMs(date: Date, config: DisplayTimeConfig): number {
  const parts = partsFor(date, offsetPartsFormatter(config));
  return parts ? partsUtcMs(parts) - date.getTime() : 0;
}

export function localDateTimeInputToDisplayIso(value: string): string | null {
  if (!value) return null;
  const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
  if (!match) return null;
  const [, year, month, day, hour, minute, second = "00"] = match;
  const targetLocalAsUtcMs = Date.UTC(
    Number(year),
    Number(month) - 1,
    Number(day),
    Number(hour),
    Number(minute),
    Number(second),
  );
  const config = displayTimeConfig();
  let utcMs = targetLocalAsUtcMs - timeZoneOffsetMs(new Date(targetLocalAsUtcMs), config);
  for (let index = 0; index < 3; index += 1) {
    utcMs = targetLocalAsUtcMs - timeZoneOffsetMs(new Date(utcMs), config);
  }
  const date = new Date(utcMs);
  const normalized = fmtDateTimeLocalInput(date);
  return Number.isNaN(date.getTime()) || normalized !== `${year}-${month}-${day}T${hour}:${minute}` ? null : date.toISOString();
}
