/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Arquivo: src/features/agenda/composables/agendaEventHelpers.js
| Data: 2026-05-04
|
| Helpers PUROS extraídos do AgendaEventDialog.vue (sub-sessão 1A do
| refator A66 — vide HANDOFF.md). Sem dependência de Vue ou de refs
| reativos. Recebem entrada → retornam saída. Testáveis isoladamente.
|
| O módulo cobre 4 categorias:
|   1. Formatters de data/hora/duração/moeda (fmt*)
|   2. Parsers/conversores (hhmmToMin, minToHHMM, isoToHHMM, ...)
|   3. Predicados (isPast, isNativeSession, isForaDoPlano)
|   4. Cálculos (calcFinalPrice, calcMinutes, addMinutesDate)
|   5. Mapeamentos de status (labelStatusSessao, statusSeverity,
|      statusExtraClass) — fixos por design.
|
| Próximas etapas (1B/1C) extraem state + computeds + handlers reativos
| num composable factory que dependerá deste módulo.
|--------------------------------------------------------------------------
*/

// ────────────────────────────────────────────────────────────────────────
// Identidade / texto
// ────────────────────────────────────────────────────────────────────────

/**
 * Iniciais do paciente pra avatar fallback (ex. "Ana Souza" → "AS").
 * Trata "" / null retornando '?'.
 */
export function patientInitials(nome) {
    const parts = String(nome || '')
        .trim()
        .split(/\s+/)
        .filter(Boolean);
    if (!parts.length) return '?';
    if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
    return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}

// ────────────────────────────────────────────────────────────────────────
// Formatters — moeda, hora, data, duração
// ────────────────────────────────────────────────────────────────────────

/** Formata número como BRL (R$ 1.234,56). null/undefined → '—'. */
export function fmtBRL(v) {
    if (v == null) return '—';
    return Number(v).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
}

/**
 * Hora compacta pra labels de jornada/duração (ex. "9h", "14h30").
 * Suporta entrada "HH:MM" ou "HH:MM:SS".
 */
export function fmtJornadaHora(hhmm) {
    const [h, m] = String(hhmm || '00:00')
        .slice(0, 5)
        .split(':')
        .map(Number);
    return m > 0 ? `${h}h${String(m).padStart(2, '0')}` : `${h}h`;
}

/** Data BR curta (15 mai 2026). Aceita Date ou string ISO. */
export function fmtDateBR(d) {
    const dt = d instanceof Date ? d : new Date(d);
    return dt.toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric' });
}

/** Data BR longa (sex, 15 mai). Aceita Date ou string ISO. */
export function fmtDateBRLong(d) {
    const dt = d instanceof Date ? d : new Date(d);
    return dt.toLocaleDateString('pt-BR', { weekday: 'short', day: '2-digit', month: 'short' });
}

/** Hora HH:MM. null → '—'. */
export function fmtTime(d) {
    if (!d) return '—';
    return new Date(d).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
}

/** Duração legível (90 → "1h 30min", 45 → "45min", 0/null → "—"). */
export function fmtDuracao(min) {
    const m = Number(min || 0);
    if (!m) return '—';
    const h = Math.floor(m / 60);
    const r = m % 60;
    if (h && r) return `${h}h ${r}min`;
    if (h) return `${h}h`;
    return `${r}min`;
}

/** Hora da série truncada em HH:MM (descarta segundos do TIME). */
export function fmtSerieHora(hora) {
    if (!hora) return '—';
    return String(hora).slice(0, 5);
}

/** Dia da semana 0-6 → nome lowercase ('domingo'..'sábado'). */
export function nomeDiaSemana(dow) {
    const nomes = ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'];
    return nomes[Number(dow ?? 0)] ?? '—';
}

/** Weekday curto a partir de ISO ('seg', 'ter', ...). */
export function fmtWeekdayShort(iso) {
    return new Date(iso).toLocaleDateString('pt-BR', { weekday: 'short' }).replace('.', '').slice(0, 3);
}

/** Dia do mês a partir de ISO. */
export function fmtDayNum(iso) {
    return new Date(iso).getDate();
}

/** Mês curto a partir de ISO ('mai', 'jun', ...). */
export function fmtMonthShort(iso) {
    return new Date(iso).toLocaleDateString('pt-BR', { month: 'short' }).replace('.', '');
}

// ────────────────────────────────────────────────────────────────────────
// Parsers / conversores
// ────────────────────────────────────────────────────────────────────────

/** "HH:MM" → minutos do dia. Trata null/inválido como 0. */
export function hhmmToMin(hhmm) {
    const [h, m] = String(hhmm || '00:00')
        .slice(0, 5)
        .split(':')
        .map(Number);
    return (h || 0) * 60 + (m || 0);
}

/** Minutos do dia → "HH:MM" zero-padded. Wrapping em 24h. */
export function minToHHMM(min) {
    const h = Math.floor(min / 60) % 24;
    const m = min % 60;
    return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
}

/**
 * Extrai HH:MM de um ISO timestamp respeitando timezone:
 *   - Se traz Z ou ±HH:MM no final → converte pra timezone local
 *   - Se for ISO sem timezone (ex: "2026-05-15T14:30:00") → lê os
 *     dígitos diretamente (evita drift quando o backend já mandou
 *     hora "como deveria aparecer").
 */
export function isoToHHMM(iso) {
    if (!iso) return null;
    const s = String(iso);
    if (s.endsWith('Z') || /[+-]\d{2}:\d{2}$/.test(s)) {
        const d = new Date(s);
        return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
    }
    const match = s.match(/T(\d{2}):(\d{2})/);
    if (match) return `${match[1]}:${match[2]}`;
    const d = new Date(s);
    return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}

// ────────────────────────────────────────────────────────────────────────
// Predicados
// ────────────────────────────────────────────────────────────────────────

/** Data já passou (em relação ao now). null/falsy → false. */
export function isPast(iso) {
    return iso ? new Date(iso) < new Date() : false;
}

/**
 * Retorna true se o commitment é uma "sessão nativa" (categoria especial
 * que requer paciente vinculado e habilita financeiro/recorrência).
 * Schema: commitments.native_key = 'session' (case-insensitive).
 */
export function isNativeSession(c) {
    return String(c?.native_key || '').toLowerCase() === 'session';
}

/**
 * Verifica se uma data está fora do plano (após dataLimiteManual).
 * dataLimiteManual=null → tudo dentro do plano (false).
 * Antes a função era impura (lia dataLimiteManual.value de ref); agora
 * recebe o valor explicitamente pra ser testável.
 */
export function isForaDoPlano(d, dataLimiteManual) {
    if (!dataLimiteManual) return false;
    return new Date(d) > new Date(dataLimiteManual);
}

// ────────────────────────────────────────────────────────────────────────
// Cálculos
// ────────────────────────────────────────────────────────────────────────

/** Adiciona N minutos a uma data, retorna NOVA Date (não muta entrada). */
export function addMinutesDate(date, min) {
    const d = new Date(date);
    d.setMinutes(d.getMinutes() + Number(min || 0));
    return d;
}

/**
 * Diferença em minutos (b - a) entre duas datas/strings ISO.
 * Negativos viram 0 (proteção contra range invertido).
 * Erros (datas inválidas) → null.
 */
export function calcMinutes(a, b) {
    try {
        if (!a || !b) return null;
        const ms = new Date(b).getTime() - new Date(a).getTime();
        return Math.max(0, Math.round(ms / 60000));
    } catch {
        return null;
    }
}

/**
 * Preço final de um item de billing aplicando desconto percentual e flat.
 *   subtotal = unit_price * quantity
 *   final    = max(0, subtotal - subtotal*pct% - flat)
 * Garante não-negativo (descontos > subtotal viram zero).
 */
export function calcFinalPrice(unit_price, quantity, discount_pct, discount_flat) {
    const subtotal = Number(unit_price) * Number(quantity);
    const discPct = subtotal * (Number(discount_pct ?? 0) / 100);
    const discFlat = Number(discount_flat ?? 0);
    return Math.max(0, subtotal - discPct - discFlat);
}

// ────────────────────────────────────────────────────────────────────────
// Mapeamentos de status da sessão
// ────────────────────────────────────────────────────────────────────────

const STATUS_LABEL_MAP = Object.freeze({
    agendado: 'Agendado',
    realizado: 'Realizado',
    faltou: 'Faltou',
    cancelado: 'Cancelado',
    remarcado: 'Remarcado'
});

/** Status enum → label legível. Desconhecido → '—'. */
export function labelStatusSessao(v) {
    return STATUS_LABEL_MAP[v] || '—';
}

/** Status → severity do PrimeVue Tag (info/success/warn/danger/secondary). */
export function statusSeverity(v) {
    if (v === 'agendado') return 'info';
    if (v === 'realizado') return 'success';
    if (v === 'faltou') return 'warn';
    if (v === 'cancelado') return 'danger';
    if (v === 'remarcado') return 'secondary'; // cor real via classe CSS
    return 'secondary';
}

/**
 * Classe CSS extra pra status que precisam de cor custom (PrimeVue
 * severity não tem roxo nativo).
 */
export function statusExtraClass(v) {
    return v === 'remarcado' ? 'tag-remarcado' : '';
}
